@pear-protocol/hyperliquid-sdk 0.1.0 → 0.1.3

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 CHANGED
@@ -316,10 +316,10 @@ const getMarketInfoFromSymbol = (symbol) => {
316
316
  const name = symbol.slice(separatorIndex + 1);
317
317
  return {
318
318
  symbolName: name || symbol,
319
- marketName: prefix || 'hyperliquid',
319
+ marketName: prefix || '',
320
320
  };
321
321
  }
322
- return { symbolName: symbol, marketName: 'hyperliquid' };
322
+ return { symbolName: symbol, marketName: '' };
323
323
  };
324
324
 
325
325
  /**
@@ -341,40 +341,60 @@ function symbolsMatch(assetName, searchSymbol) {
341
341
  */
342
342
  class TokenMetadataExtractor {
343
343
  /**
344
- * Extracts comprehensive token metadata
344
+ * Checks if token data is available in aggregated universe assets
345
+ * @param symbol - Token symbol
346
+ * @param perpMetaAssets - Aggregated universe assets
347
+ * @returns boolean indicating if token exists in universe
348
+ */
349
+ static isTokenAvailable(symbol, perpMetaAssets) {
350
+ if (!perpMetaAssets)
351
+ return false;
352
+ return perpMetaAssets.some((asset) => symbolsMatch(asset.name, symbol));
353
+ }
354
+ /**
355
+ * Extracts token metadata using DEX-aware lookup (correctly matches perpMetas to assetContexts)
345
356
  * @param symbol - Token symbol (e.g., "BTC", "TSLA")
346
- * @param perpMetaAssets - Aggregated universe assets (flattened across dexes)
347
- * @param finalAssetContexts - Aggregated asset contexts (flattened across dexes)
357
+ * @param perpMetasByDex - Map of DEX name to UniverseAsset[]
358
+ * @param assetContextsByDex - Map of DEX name to WebData3AssetCtx[]
348
359
  * @param allMids - AllMids data containing current prices
349
360
  * @param activeAssetData - Optional active asset data containing leverage information
361
+ * @param finalAtOICaps - Optional array of symbols at OI caps
350
362
  * @returns TokenMetadata or null if token not found
351
363
  */
352
- static extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, finalAtOICaps) {
353
- if (!perpMetaAssets || !finalAssetContexts || !allMids) {
354
- return null;
364
+ static extractTokenMetadataByDex(symbol, perpMetasByDex, assetContextsByDex, allMids, activeAssetData, finalAtOICaps) {
365
+ let foundDexName = null;
366
+ let foundAssetIndex = -1;
367
+ let foundAsset = null;
368
+ for (const [dexName, assets] of perpMetasByDex) {
369
+ const assetIndex = assets.findIndex((asset) => symbolsMatch(asset.name, symbol));
370
+ if (assetIndex !== -1) {
371
+ foundDexName = dexName;
372
+ foundAssetIndex = assetIndex;
373
+ foundAsset = assets[assetIndex];
374
+ break;
375
+ }
355
376
  }
356
- const universeIndex = perpMetaAssets.findIndex((asset) => symbolsMatch(asset.name, symbol));
357
- if (universeIndex === -1) {
377
+ if (!foundDexName || foundAssetIndex === -1 || !foundAsset) {
358
378
  return null;
359
379
  }
360
- const universeAsset = perpMetaAssets[universeIndex];
361
- const assetCtx = finalAssetContexts[universeIndex];
380
+ const dexContexts = assetContextsByDex.get(foundDexName);
381
+ const assetCtx = dexContexts === null || dexContexts === void 0 ? void 0 : dexContexts[foundAssetIndex];
362
382
  if (!assetCtx) {
363
383
  return null;
364
384
  }
365
385
  // Get current price - prefer assetCtx.midPx as it's already index-matched,
366
386
  // fall back to allMids lookup if midPx is null
367
- const actualSymbol = universeAsset.name;
387
+ const actualSymbol = foundAsset.name;
368
388
  let currentPrice = 0;
369
- // Primary source: assetCtx.midPx (already properly indexed)
370
- if (assetCtx.midPx) {
371
- currentPrice = parseFloat(assetCtx.midPx);
372
- }
373
- // Fallback: allMids lookup
389
+ // Fallback: assetCtx.midPx (already properly indexed)
374
390
  if (!currentPrice || isNaN(currentPrice)) {
375
391
  const currentPriceStr = allMids.mids[actualSymbol] || allMids.mids[symbol];
376
392
  currentPrice = currentPriceStr ? parseFloat(currentPriceStr) : 0;
377
393
  }
394
+ // Primary source: allMids lookup
395
+ if (assetCtx.midPx) {
396
+ currentPrice = parseFloat(assetCtx.midPx);
397
+ }
378
398
  // Get previous day price
379
399
  const prevDayPrice = parseFloat(assetCtx.prevDayPx);
380
400
  // Calculate 24h price change
@@ -390,20 +410,17 @@ class TokenMetadataExtractor {
390
410
  const maxTradeSzs = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.maxTradeSzs;
391
411
  const availableToTrade = tokenActiveData === null || tokenActiveData === void 0 ? void 0 : tokenActiveData.availableToTrade;
392
412
  const { symbolName, marketName } = getMarketInfoFromSymbol(actualSymbol);
393
- const assetName = symbolName;
394
413
  return {
395
- assetName,
414
+ assetName: foundAsset.name,
396
415
  symbolName,
397
416
  marketName,
398
- isAtOiCaps: finalAtOICaps
399
- ? finalAtOICaps.includes(symbol)
400
- : false,
417
+ isAtOiCaps: finalAtOICaps ? finalAtOICaps.includes(symbol) : false,
401
418
  currentPrice,
402
419
  prevDayPrice,
403
420
  priceChange24h,
404
421
  priceChange24hPercent,
405
422
  netFunding,
406
- maxLeverage: universeAsset.maxLeverage,
423
+ maxLeverage: foundAsset.maxLeverage,
407
424
  markPrice,
408
425
  oraclePrice,
409
426
  openInterest: assetCtx.openInterest,
@@ -411,54 +428,24 @@ class TokenMetadataExtractor {
411
428
  leverage,
412
429
  maxTradeSzs,
413
430
  availableToTrade,
414
- collateralToken: universeAsset.collateralToken,
431
+ collateralToken: foundAsset.collateralToken,
415
432
  };
416
433
  }
417
- /**
418
- * Extracts metadata for multiple tokens
419
- * @param tokens - Array of token strings (e.g., "BTC", "TSLA")
420
- * @param perpMetaAssets - Aggregated universe assets
421
- * @param finalAssetContexts - Aggregated asset contexts
422
- * @param allMids - AllMids data
423
- * @param activeAssetData - Optional active asset data containing leverage information
424
- * @returns Record of token string to TokenMetadata (keys match input tokens exactly)
425
- */
426
- static extractMultipleTokensMetadata(tokens, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, finalAtOICaps) {
427
- const result = {};
428
- for (const token of tokens) {
429
- result[token] = this.extractTokenMetadata(token, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, finalAtOICaps);
430
- }
431
- return result;
432
- }
433
- /**
434
- * Checks if token data is available in aggregated universe assets
435
- * @param symbol - Token symbol
436
- * @param perpMetaAssets - Aggregated universe assets
437
- * @returns boolean indicating if token exists in universe
438
- */
439
- static isTokenAvailable(symbol, perpMetaAssets) {
440
- if (!perpMetaAssets)
441
- return false;
442
- return perpMetaAssets.some((asset) => symbolsMatch(asset.name, symbol));
443
- }
444
434
  }
445
-
435
+ // Helper functions for token metadata
446
436
  const buildOiCapSet = (finalAtOICaps) => new Set((finalAtOICaps !== null && finalAtOICaps !== void 0 ? finalAtOICaps : [])
447
437
  .filter(Boolean)
448
438
  .map((value) => value.toUpperCase()));
449
- const isAtOiCaps = (symbol, symbolName, oiCapSet) => oiCapSet.has(symbol.toUpperCase()) ||
450
- oiCapSet.has(symbolName.toUpperCase());
439
+ const isAtOiCaps = (assetName, oiCapSet) => oiCapSet.has(assetName.toUpperCase());
451
440
  const applyMetadataContext = (symbol, metadata, oiCapSet) => {
452
441
  if (!metadata)
453
442
  return null;
454
443
  const { symbolName, marketName } = getMarketInfoFromSymbol(symbol);
455
- const assetName = symbolName;
456
444
  return {
457
445
  ...metadata,
458
- assetName,
459
446
  symbolName,
460
447
  marketName,
461
- isAtOiCaps: isAtOiCaps(symbol, symbolName, oiCapSet),
448
+ isAtOiCaps: isAtOiCaps(symbol, oiCapSet),
462
449
  };
463
450
  };
464
451
  const applyOiCapsToMetadataMap = (tokenMetadata, finalAtOICaps) => {
@@ -472,30 +459,79 @@ const applyOiCapsToMetadataMap = (tokenMetadata, finalAtOICaps) => {
472
459
  });
473
460
  return next;
474
461
  };
475
- const buildTokenMetadataMap = ({ perpMetaAssets, finalAssetContexts, allMids, activeAssetData, finalAtOICaps, }) => {
476
- if (!perpMetaAssets || !finalAssetContexts || !allMids) {
462
+ const shouldSkipToken = (asset, oiCapSet) => {
463
+ if (asset.isDelisted)
464
+ return true;
465
+ if (isAtOiCaps(asset.name, oiCapSet))
466
+ return true;
467
+ return false;
468
+ };
469
+ const buildTokenMetadataMap = ({ perpMetasByDex, assetContextsByDex, allMids, activeAssetData, finalAtOICaps, }) => {
470
+ if (!perpMetasByDex || !assetContextsByDex || !allMids) {
477
471
  return {};
478
472
  }
479
473
  const oiCapSet = buildOiCapSet(finalAtOICaps);
480
- const symbols = perpMetaAssets.map((asset) => asset.name);
481
- const metadataMap = TokenMetadataExtractor.extractMultipleTokensMetadata(symbols, perpMetaAssets, finalAssetContexts, allMids, activeAssetData, finalAtOICaps);
482
- symbols.forEach((symbol) => {
483
- metadataMap[symbol] = applyMetadataContext(symbol, metadataMap[symbol], oiCapSet);
484
- });
474
+ const metadataMap = {};
475
+ // Iterate through all DEXes and their assets
476
+ for (const [, assets] of perpMetasByDex) {
477
+ for (const asset of assets) {
478
+ if (shouldSkipToken(asset, oiCapSet)) {
479
+ continue;
480
+ }
481
+ const symbol = asset.name;
482
+ const metadata = TokenMetadataExtractor.extractTokenMetadataByDex(symbol, perpMetasByDex, assetContextsByDex, allMids, activeAssetData, finalAtOICaps);
483
+ metadataMap[symbol] = applyMetadataContext(symbol, metadata, oiCapSet);
484
+ }
485
+ }
485
486
  return metadataMap;
486
487
  };
487
- const updateTokenMetadataForSymbols = (prev, symbols, { perpMetaAssets, finalAssetContexts, allMids, activeAssetData, finalAtOICaps, }) => {
488
- if (!perpMetaAssets || !finalAssetContexts || !allMids) {
488
+ const updateTokenMetadataForSymbols = (prev, symbols, { perpMetasByDex, assetContextsByDex, allMids, activeAssetData, finalAtOICaps, }) => {
489
+ if (!perpMetasByDex || !assetContextsByDex || !allMids) {
489
490
  return prev;
490
491
  }
491
492
  const oiCapSet = buildOiCapSet(finalAtOICaps);
492
493
  const next = { ...prev };
493
494
  symbols.forEach((symbol) => {
494
- next[symbol] = applyMetadataContext(symbol, TokenMetadataExtractor.extractTokenMetadata(symbol, perpMetaAssets, finalAssetContexts, allMids, activeAssetData), oiCapSet);
495
+ // Find asset in any DEX
496
+ let foundAsset;
497
+ for (const [, assets] of perpMetasByDex) {
498
+ foundAsset = assets.find((a) => a.name === symbol);
499
+ if (foundAsset)
500
+ break;
501
+ }
502
+ if (foundAsset && shouldSkipToken(foundAsset, oiCapSet)) {
503
+ delete next[symbol];
504
+ return;
505
+ }
506
+ next[symbol] = applyMetadataContext(symbol, TokenMetadataExtractor.extractTokenMetadataByDex(symbol, perpMetasByDex, assetContextsByDex, allMids, activeAssetData, finalAtOICaps), oiCapSet);
495
507
  });
496
508
  return next;
497
509
  };
498
- const useHyperliquidData = create((set, get) => ({
510
+ const refreshTokenMetadata = (state, overrides, options) => {
511
+ var _a, _b, _c, _d, _e, _f, _g, _h;
512
+ const inputs = {
513
+ perpMetaAssets: (_a = overrides.perpMetaAssets) !== null && _a !== void 0 ? _a : state.perpMetaAssets,
514
+ finalAssetContexts: (_b = overrides.finalAssetContexts) !== null && _b !== void 0 ? _b : state.finalAssetContexts,
515
+ allMids: (_c = overrides.allMids) !== null && _c !== void 0 ? _c : state.allMids,
516
+ activeAssetData: (_d = overrides.activeAssetData) !== null && _d !== void 0 ? _d : state.activeAssetData,
517
+ finalAtOICaps: (_e = overrides.finalAtOICaps) !== null && _e !== void 0 ? _e : state.finalAtOICaps,
518
+ // DEX-aware inputs
519
+ perpMetasByDex: (_f = overrides.perpMetasByDex) !== null && _f !== void 0 ? _f : state.perpMetasByDex,
520
+ assetContextsByDex: (_g = overrides.assetContextsByDex) !== null && _g !== void 0 ? _g : state.assetContextsByDex,
521
+ };
522
+ if (!inputs.perpMetasByDex || !inputs.assetContextsByDex || !inputs.allMids) {
523
+ return state.tokenMetadata;
524
+ }
525
+ if (options === null || options === void 0 ? void 0 : options.oiCapsOnly) {
526
+ return applyOiCapsToMetadataMap(state.tokenMetadata, inputs.finalAtOICaps);
527
+ }
528
+ if ((_h = options === null || options === void 0 ? void 0 : options.symbols) === null || _h === void 0 ? void 0 : _h.length) {
529
+ return updateTokenMetadataForSymbols(state.tokenMetadata, options.symbols, inputs);
530
+ }
531
+ return buildTokenMetadataMap(inputs);
532
+ };
533
+
534
+ const useHyperliquidData = create((set) => ({
499
535
  allMids: null,
500
536
  activeAssetData: null,
501
537
  candleData: null,
@@ -505,45 +541,30 @@ const useHyperliquidData = create((set, get) => ({
505
541
  rawClearinghouseStates: null,
506
542
  perpMetaAssets: null,
507
543
  tokenMetadata: {},
544
+ perpDexs: null,
545
+ perpMetasByDex: null,
546
+ assetContextsByDex: null,
508
547
  setAllMids: (value) => set((state) => ({
509
548
  allMids: value,
510
- tokenMetadata: buildTokenMetadataMap({
511
- perpMetaAssets: state.perpMetaAssets,
512
- finalAssetContexts: state.finalAssetContexts,
513
- allMids: value,
514
- activeAssetData: state.activeAssetData,
515
- finalAtOICaps: state.finalAtOICaps,
516
- }),
549
+ tokenMetadata: refreshTokenMetadata(state, { allMids: value }),
517
550
  })),
518
551
  setActiveAssetData: (value) => set((state) => {
519
552
  const activeAssetData = typeof value === 'function' ? value(state.activeAssetData) : value;
520
553
  return {
521
554
  activeAssetData,
522
- tokenMetadata: buildTokenMetadataMap({
523
- perpMetaAssets: state.perpMetaAssets,
524
- finalAssetContexts: state.finalAssetContexts,
525
- allMids: state.allMids,
526
- activeAssetData,
527
- finalAtOICaps: state.finalAtOICaps,
528
- }),
555
+ tokenMetadata: refreshTokenMetadata(state, { activeAssetData }),
529
556
  };
530
557
  }),
531
558
  deleteActiveAssetData: (key) => {
532
559
  set((state) => {
533
560
  if (!state.activeAssetData || !(key in state.activeAssetData)) {
534
- return state; // No change if key doesn't exist
561
+ return state;
535
562
  }
536
- const updated = { ...state.activeAssetData };
537
- delete updated[key];
563
+ const activeAssetData = { ...state.activeAssetData };
564
+ delete activeAssetData[key];
538
565
  return {
539
- activeAssetData: updated,
540
- tokenMetadata: updateTokenMetadataForSymbols(state.tokenMetadata, [key], {
541
- perpMetaAssets: state.perpMetaAssets,
542
- finalAssetContexts: state.finalAssetContexts,
543
- allMids: state.allMids,
544
- activeAssetData: updated,
545
- finalAtOICaps: state.finalAtOICaps,
546
- }),
566
+ activeAssetData,
567
+ tokenMetadata: refreshTokenMetadata(state, { activeAssetData }, { symbols: [key] }),
547
568
  };
548
569
  });
549
570
  },
@@ -568,47 +589,41 @@ const useHyperliquidData = create((set, get) => ({
568
589
  setCandleData: (value) => set({ candleData: value }),
569
590
  upsertActiveAssetData: (key, value) => set((state) => {
570
591
  var _a;
571
- const activeAssetData = {
572
- ...((_a = state.activeAssetData) !== null && _a !== void 0 ? _a : {}),
573
- [key]: value,
574
- };
592
+ const activeAssetData = { ...((_a = state.activeAssetData) !== null && _a !== void 0 ? _a : {}), [key]: value };
575
593
  return {
576
594
  activeAssetData,
577
- tokenMetadata: updateTokenMetadataForSymbols(state.tokenMetadata, [key], {
578
- perpMetaAssets: state.perpMetaAssets,
579
- finalAssetContexts: state.finalAssetContexts,
580
- allMids: state.allMids,
581
- activeAssetData,
582
- finalAtOICaps: state.finalAtOICaps,
583
- }),
595
+ tokenMetadata: refreshTokenMetadata(state, { activeAssetData }, { symbols: [key] }),
584
596
  };
585
597
  }),
586
598
  setFinalAssetContexts: (value) => set((state) => ({
587
599
  finalAssetContexts: value,
588
- tokenMetadata: buildTokenMetadataMap({
589
- perpMetaAssets: state.perpMetaAssets,
590
- finalAssetContexts: value,
591
- allMids: state.allMids,
592
- activeAssetData: state.activeAssetData,
593
- finalAtOICaps: state.finalAtOICaps,
594
- }),
600
+ tokenMetadata: refreshTokenMetadata(state, { finalAssetContexts: value }),
595
601
  })),
596
602
  setFinalAtOICaps: (value) => set((state) => ({
597
603
  finalAtOICaps: value,
598
- tokenMetadata: applyOiCapsToMetadataMap(state.tokenMetadata, value),
604
+ tokenMetadata: refreshTokenMetadata(state, { finalAtOICaps: value }, { oiCapsOnly: true }),
599
605
  })),
600
606
  setAggregatedClearingHouseState: (value) => set({ aggregatedClearingHouseState: value }),
601
607
  setRawClearinghouseStates: (value) => set({ rawClearinghouseStates: value }),
602
608
  setPerpMetaAssets: (value) => set((state) => ({
603
609
  perpMetaAssets: value,
604
- tokenMetadata: buildTokenMetadataMap({
605
- perpMetaAssets: value,
606
- finalAssetContexts: state.finalAssetContexts,
607
- allMids: state.allMids,
608
- activeAssetData: state.activeAssetData,
609
- finalAtOICaps: state.finalAtOICaps,
610
+ tokenMetadata: refreshTokenMetadata(state, { perpMetaAssets: value }),
611
+ })),
612
+ setPerpDexs: (value) => set({ perpDexs: value }),
613
+ setPerpMetasByDex: (value) => set((state) => ({
614
+ perpMetasByDex: value,
615
+ tokenMetadata: refreshTokenMetadata(state, {
616
+ perpMetasByDex: value,
617
+ assetContextsByDex: state.assetContextsByDex,
618
+ }),
619
+ })),
620
+ setAssetContextsByDex: (value) => set((state) => ({
621
+ assetContextsByDex: value,
622
+ tokenMetadata: refreshTokenMetadata(state, {
623
+ assetContextsByDex: value,
624
+ perpMetasByDex: state.perpMetasByDex,
610
625
  }),
611
- }))
626
+ })),
612
627
  }));
613
628
 
614
629
  /**
@@ -871,7 +886,7 @@ const useUserSelection$1 = create((set, get) => ({
871
886
  }));
872
887
 
873
888
  const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }) => {
874
- const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAssetContexts, setFinalAtOICaps, setAggregatedClearingHouseState, setRawClearinghouseStates, } = useHyperliquidData();
889
+ const { setAllMids, setActiveAssetData, upsertActiveAssetData, setCandleData, deleteCandleSymbol, deleteActiveAssetData, addCandleData, setFinalAtOICaps, setAggregatedClearingHouseState, setRawClearinghouseStates, setAssetContextsByDex, } = useHyperliquidData();
875
890
  const { setSpotState } = useUserData();
876
891
  const { candleInterval } = useUserSelection$1();
877
892
  const userSummary = useUserData((state) => state.accountSummary);
@@ -924,22 +939,13 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
924
939
  case 'allDexsAssetCtxs':
925
940
  {
926
941
  const data = response.data;
927
- // Filter out hyna to match perpMetaAssets filtering
928
- const FILTERED_DEX_PREFIXES = ['hyna'];
929
- const filtered = (data.ctxs || [])
930
- .filter(([prefix]) => !FILTERED_DEX_PREFIXES.includes((prefix || '').toLowerCase()))
931
- .sort((a, b) => {
932
- // Sort to match perpMetaAssets order: default market first, then alphabetically
933
- const prefixA = a[0] || '';
934
- const prefixB = b[0] || '';
935
- if (prefixA === '' && prefixB !== '')
936
- return -1;
937
- if (prefixA !== '' && prefixB === '')
938
- return 1;
939
- return prefixA.localeCompare(prefixB);
942
+ // Store by DEX name, mapping '' to 'HYPERLIQUID'
943
+ const assetContextsByDex = new Map();
944
+ data.ctxs.forEach(([dexKey, ctxs]) => {
945
+ const dexName = dexKey === '' ? 'HYPERLIQUID' : dexKey;
946
+ assetContextsByDex.set(dexName, ctxs || []);
940
947
  });
941
- const finalAssetContexts = filtered.flatMap(([, ctxs]) => ctxs || []);
942
- setFinalAssetContexts(finalAssetContexts);
948
+ setAssetContextsByDex(assetContextsByDex);
943
949
  }
944
950
  break;
945
951
  case 'allDexsClearinghouseState':
@@ -1020,10 +1026,10 @@ const useHyperliquidNativeWebSocket = ({ address, enabled = true, onUserFills, }
1020
1026
  setAllMids,
1021
1027
  upsertActiveAssetData,
1022
1028
  addCandleData,
1023
- setFinalAssetContexts,
1024
1029
  setFinalAtOICaps,
1025
1030
  setAggregatedClearingHouseState,
1026
1031
  setRawClearinghouseStates,
1032
+ setAssetContextsByDex,
1027
1033
  setSpotState,
1028
1034
  onUserFills,
1029
1035
  ]);
@@ -1507,7 +1513,36 @@ const useOpenOrders = () => {
1507
1513
  };
1508
1514
 
1509
1515
  const useUserSelection = () => {
1510
- return useUserSelection$1();
1516
+ const longTokens = useUserSelection$1((s) => s.longTokens);
1517
+ const shortTokens = useUserSelection$1((s) => s.shortTokens);
1518
+ const candleInterval = useUserSelection$1((s) => s.candleInterval);
1519
+ const openTokenSelector = useUserSelection$1((s) => s.openTokenSelector);
1520
+ const selectorConfig = useUserSelection$1((s) => s.selectorConfig);
1521
+ const openConflictModal = useUserSelection$1((s) => s.openConflictModal);
1522
+ const setLongTokens = useUserSelection$1((s) => s.setLongTokens);
1523
+ const setShortTokens = useUserSelection$1((s) => s.setShortTokens);
1524
+ const setCandleInterval = useUserSelection$1((s) => s.setCandleInterval);
1525
+ const setTokenSelections = useUserSelection$1((s) => s.setTokenSelections);
1526
+ const setOpenTokenSelector = useUserSelection$1((s) => s.setOpenTokenSelector);
1527
+ const setSelectorConfig = useUserSelection$1((s) => s.setSelectorConfig);
1528
+ const setOpenConflictModal = useUserSelection$1((s) => s.setOpenConflictModal);
1529
+ const addToken = useUserSelection$1((s) => s.addToken);
1530
+ return {
1531
+ longTokens,
1532
+ shortTokens,
1533
+ candleInterval,
1534
+ openTokenSelector,
1535
+ selectorConfig,
1536
+ openConflictModal,
1537
+ setLongTokens,
1538
+ setShortTokens,
1539
+ setCandleInterval,
1540
+ setTokenSelections,
1541
+ setOpenTokenSelector,
1542
+ setSelectorConfig,
1543
+ setOpenConflictModal,
1544
+ addToken,
1545
+ };
1511
1546
  };
1512
1547
 
1513
1548
  const useTokenSelectionMetadataStore = create((set) => ({
@@ -1523,11 +1558,10 @@ const useTokenSelectionMetadataStore = create((set) => ({
1523
1558
  volume: "0",
1524
1559
  sumNetFunding: 0,
1525
1560
  maxLeverage: 0,
1526
- minMargin: 0,
1561
+ minSize: {},
1527
1562
  leverageMatched: true,
1528
- recompute: ({ perpMetaAssets, tokenMetadata, marketData, longTokens, shortTokens, }) => {
1563
+ recompute: ({ tokenMetadata, marketData, longTokens, shortTokens, }) => {
1529
1564
  const isPriceDataReady = Object.keys(tokenMetadata).length > 0;
1530
- // Get token symbols for lookups
1531
1565
  const longSymbols = longTokens.map((t) => t.symbol);
1532
1566
  const shortSymbols = shortTokens.map((t) => t.symbol);
1533
1567
  // Get metadata
@@ -1650,24 +1684,37 @@ const useTokenSelectionMetadataStore = create((set) => ({
1650
1684
  })();
1651
1685
  // Max leverage (maximum across all tokens)
1652
1686
  const maxLeverage = (() => {
1653
- if (!perpMetaAssets)
1687
+ if (!tokenMetadata)
1654
1688
  return 0;
1655
1689
  const allSymbols = [...longSymbols, ...shortSymbols];
1656
1690
  if (allSymbols.length === 0)
1657
1691
  return 0;
1658
1692
  let maxLev = 0;
1659
1693
  allSymbols.forEach((symbol) => {
1660
- const tokenUniverse = perpMetaAssets.find((u) => u.name === symbol);
1661
- if (tokenUniverse === null || tokenUniverse === void 0 ? void 0 : tokenUniverse.maxLeverage) {
1662
- maxLev = Math.max(maxLev, tokenUniverse.maxLeverage);
1694
+ var _a;
1695
+ const tokenDetail = tokenMetadata[symbol];
1696
+ if ((_a = tokenDetail === null || tokenDetail === void 0 ? void 0 : tokenDetail.leverage) === null || _a === void 0 ? void 0 : _a.value) {
1697
+ maxLev = Math.max(maxLev, tokenDetail.leverage.value);
1663
1698
  }
1664
1699
  });
1665
1700
  return maxLev;
1666
1701
  })();
1667
- // Min margin (10 * total number of tokens)
1668
- const minMargin = (() => {
1669
- const totalTokenCount = longTokens.length + shortTokens.length;
1670
- return 10 * totalTokenCount;
1702
+ const minSize = (() => {
1703
+ const allSymbols = [...longSymbols, ...shortSymbols];
1704
+ const collateralCounts = {};
1705
+ allSymbols.forEach((symbol) => {
1706
+ var _a;
1707
+ const collateralToken = (_a = tokenMetadata[symbol]) === null || _a === void 0 ? void 0 : _a.collateralToken;
1708
+ if (collateralToken) {
1709
+ collateralCounts[collateralToken] =
1710
+ (collateralCounts[collateralToken] || 0) + 1;
1711
+ }
1712
+ });
1713
+ const minSizeMap = {};
1714
+ Object.entries(collateralCounts).forEach(([collateral, count]) => {
1715
+ minSizeMap[collateral] = 11 * count;
1716
+ });
1717
+ return minSizeMap;
1671
1718
  })();
1672
1719
  // Whether all tokens have matching leverage
1673
1720
  const leverageMatched = (() => {
@@ -1702,7 +1749,7 @@ const useTokenSelectionMetadataStore = create((set) => ({
1702
1749
  volume,
1703
1750
  sumNetFunding,
1704
1751
  maxLeverage,
1705
- minMargin,
1752
+ minSize,
1706
1753
  leverageMatched,
1707
1754
  });
1708
1755
  },
@@ -1717,11 +1764,10 @@ const useTokenSelectionMetadata = () => {
1717
1764
  const tokenMetadata = useHyperliquidData((state) => state.tokenMetadata);
1718
1765
  const marketData = useMarketData((state) => state.marketData);
1719
1766
  const { longTokens, shortTokens } = useUserSelection$1();
1720
- const { isLoading, isPriceDataReady, longTokensMetadata, shortTokensMetadata, weightedRatio, weightedRatio24h, priceRatio, priceRatio24h, openInterest, volume, sumNetFunding, maxLeverage, minMargin, leverageMatched, recompute, } = useTokenSelectionMetadataStore();
1767
+ const { isLoading, isPriceDataReady, longTokensMetadata, shortTokensMetadata, weightedRatio, weightedRatio24h, priceRatio, priceRatio24h, openInterest, volume, sumNetFunding, maxLeverage, minSize, leverageMatched, recompute, } = useTokenSelectionMetadataStore();
1721
1768
  // Recompute derived metadata when inputs change
1722
1769
  useEffect(() => {
1723
1770
  recompute({
1724
- perpMetaAssets,
1725
1771
  tokenMetadata,
1726
1772
  marketData: marketData || null,
1727
1773
  longTokens,
@@ -1745,8 +1791,8 @@ const useTokenSelectionMetadata = () => {
1745
1791
  volume,
1746
1792
  sumNetFunding,
1747
1793
  maxLeverage,
1748
- minMargin,
1749
1794
  leverageMatched,
1795
+ minSize,
1750
1796
  };
1751
1797
  };
1752
1798
 
@@ -5935,6 +5981,26 @@ const fetchExtraAgents = async (user) => {
5935
5981
  throw toApiError(error);
5936
5982
  }
5937
5983
  };
5984
+ /**
5985
+ * Fetch perp dexes from HyperLiquid API
5986
+ * Endpoint: https://api.hyperliquid.xyz/info
5987
+ * Payload: { "type": "perpDexs" }
5988
+ * Returns array where index 0 is null (HYPERLIQUID), index 1+ are named DEXes
5989
+ */
5990
+ const fetchPerpDexs = async () => {
5991
+ const request = { type: 'perpDexs' };
5992
+ try {
5993
+ const response = await axios$1.post('https://api.hyperliquid.xyz/info', request, { headers: { 'Content-Type': 'application/json' } });
5994
+ return {
5995
+ data: response.data,
5996
+ status: response.status,
5997
+ headers: response.headers,
5998
+ };
5999
+ }
6000
+ catch (error) {
6001
+ throw toApiError(error);
6002
+ }
6003
+ };
5938
6004
 
5939
6005
  const useHistoricalPriceData = () => {
5940
6006
  const context = useContext(PearHyperliquidContext);
@@ -6496,7 +6562,6 @@ const usePerformanceOverlays = () => {
6496
6562
  overlays.forEach(overlay => {
6497
6563
  symbols.push(overlay.symbol);
6498
6564
  });
6499
- console.log("final symbols", symbols);
6500
6565
  return symbols;
6501
6566
  }, [overlays]);
6502
6567
  return {
@@ -7172,132 +7237,62 @@ function useNotifications() {
7172
7237
  };
7173
7238
  }
7174
7239
 
7175
- // Convert a basket item to the expected type
7176
- function enrichBasketItem(item) {
7177
- const enrichedLongs = item.longAssets.map((a) => ({
7178
- ...a,
7179
- }));
7180
- const enrichedShorts = item.shortAssets.map((a) => ({
7181
- ...a,
7182
- }));
7183
- // // Determine collateral type
7184
- // const allAssets = [...enrichedLongs, ...enrichedShorts];
7185
- // const hasUsdc = allAssets.some((a) => a.collateralToken === 'USDC');
7186
- // const hasUsdh = allAssets.some((a) => a.collateralToken === 'USDH');
7187
- // let collateralType: 'USDC' | 'USDH' | 'MIXED' = 'USDC';
7188
- // if (hasUsdc && hasUsdh) {
7189
- // collateralType = 'MIXED';
7190
- // } else if (hasUsdh) {
7191
- // collateralType = 'USDH';
7192
- // }
7240
+ function enrichBasketWithMetadata(basket, tokenMetadata) {
7193
7241
  return {
7194
- ...item,
7195
- longAssets: enrichedLongs,
7196
- shortAssets: enrichedShorts,
7197
- collateralType: 'USDC', //TODO: change
7242
+ ...basket,
7243
+ longAssets: basket.longAssets.map((asset) => {
7244
+ var _a;
7245
+ return ({
7246
+ ...asset,
7247
+ metadata: (_a = tokenMetadata[asset.asset]) !== null && _a !== void 0 ? _a : null,
7248
+ });
7249
+ }),
7250
+ shortAssets: basket.shortAssets.map((asset) => {
7251
+ var _a;
7252
+ return ({
7253
+ ...asset,
7254
+ metadata: (_a = tokenMetadata[asset.asset]) !== null && _a !== void 0 ? _a : null,
7255
+ });
7256
+ }),
7198
7257
  };
7199
7258
  }
7200
- /**
7201
- * Filter baskets by collateral type
7202
- * - 'USDC': Only baskets where ALL assets use USDC (collateralType === 'USDC')
7203
- * - 'USDH': Only baskets where ALL assets use USDH (collateralType === 'USDH')
7204
- * - 'ALL' or undefined: No filtering, returns all baskets
7205
- */
7206
- function filterByCollateral(baskets, filter) {
7207
- if (!filter || filter === 'ALL') {
7208
- return baskets;
7209
- }
7210
- return baskets.filter((basket) => {
7211
- if (filter === 'USDC') {
7212
- // Include baskets that are purely USDC or have USDC assets
7213
- return (basket.collateralType === 'USDC' || basket.collateralType === 'MIXED');
7214
- }
7215
- if (filter === 'USDH') {
7216
- // Include baskets that are purely USDH or have USDH assets
7217
- return (basket.collateralType === 'USDH' || basket.collateralType === 'MIXED');
7218
- }
7219
- return true;
7220
- });
7259
+ function enrichBasketsWithMetadata(baskets, tokenMetadata) {
7260
+ return baskets.map((basket) => enrichBasketWithMetadata(basket, tokenMetadata));
7221
7261
  }
7222
- // Base selector for the full market-data payload (raw from WS)
7223
- const useMarketDataPayload = () => {
7224
- return useMarketData((s) => s.marketData);
7225
- };
7226
- // Active baskets
7227
- const useActiveBaskets = (collateralFilter) => {
7228
- const data = useMarketDataPayload();
7229
- return useMemo(() => {
7230
- if (!(data === null || data === void 0 ? void 0 : data.active))
7231
- return [];
7232
- const enriched = data.active.map((item) => enrichBasketItem(item));
7233
- return filterByCollateral(enriched, collateralFilter);
7234
- }, [data, collateralFilter]);
7235
- };
7236
- // Top gainers
7237
- const useTopGainers = (limit, collateralFilter) => {
7238
- const data = useMarketDataPayload();
7239
- return useMemo(() => {
7240
- var _a;
7241
- const list = (_a = data === null || data === void 0 ? void 0 : data.topGainers) !== null && _a !== void 0 ? _a : [];
7242
- const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
7243
- const enriched = limited.map((item) => enrichBasketItem(item));
7244
- return filterByCollateral(enriched, collateralFilter);
7245
- }, [data, limit, collateralFilter]);
7246
- };
7247
- // Top losers
7248
- const useTopLosers = (limit, collateralFilter) => {
7249
- const data = useMarketDataPayload();
7250
- return useMemo(() => {
7251
- var _a;
7252
- const list = (_a = data === null || data === void 0 ? void 0 : data.topLosers) !== null && _a !== void 0 ? _a : [];
7253
- const limited = typeof limit === 'number' ? list.slice(0, Math.max(0, limit)) : list;
7254
- const enriched = limited.map((item) => enrichBasketItem(item));
7255
- return filterByCollateral(enriched, collateralFilter);
7256
- }, [data, limit, collateralFilter]);
7257
- };
7258
- // Highlighted baskets
7259
- const useHighlightedBaskets = (collateralFilter) => {
7260
- const data = useMarketDataPayload();
7262
+ const useMarketDataHook = (options = {}) => {
7263
+ const { topGainersLimit, topLosersLimit } = options;
7264
+ const data = useMarketData((s) => s.marketData);
7265
+ const tokenMetadata = useHyperliquidData((s) => s.tokenMetadata);
7261
7266
  return useMemo(() => {
7262
- if (!(data === null || data === void 0 ? void 0 : data.highlighted))
7263
- return [];
7264
- const enriched = data.highlighted.map((item) => enrichBasketItem(item));
7265
- return filterByCollateral(enriched, collateralFilter);
7266
- }, [data, collateralFilter]);
7267
- };
7268
- // Watchlist baskets
7269
- const useWatchlistBaskets = (collateralFilter) => {
7270
- const data = useMarketDataPayload();
7271
- return useMemo(() => {
7272
- if (!(data === null || data === void 0 ? void 0 : data.watchlist))
7273
- return [];
7274
- const enriched = data.watchlist.map((item) => enrichBasketItem(item));
7275
- return filterByCollateral(enriched, collateralFilter);
7276
- }, [data, collateralFilter]);
7277
- };
7278
- // Find a basket by its exact asset composition (order-insensitive)
7279
- const useFindBasket = (longs, shorts) => {
7280
- const data = useMarketDataPayload();
7281
- return useMemo(() => {
7282
- if (!data)
7283
- return undefined;
7284
- const normalize = (arr) => Array.isArray(arr)
7285
- ? arr
7286
- .map((v) => (typeof v === 'string' ? v : v === null || v === void 0 ? void 0 : v.asset))
7287
- .filter(Boolean)
7288
- .map((s) => s.toUpperCase())
7289
- .sort()
7290
- .join(',')
7291
- : '';
7292
- const lKey = normalize(longs);
7293
- const sKey = normalize(shorts);
7294
- const match = (item) => normalize(item.longAssets) === lKey &&
7295
- normalize(item.shortAssets) === sKey;
7296
- const found = data.active.find(match) || data.highlighted.find(match);
7297
- return found
7298
- ? enrichBasketItem(found)
7299
- : undefined;
7300
- }, [data, longs, shorts]);
7267
+ var _a, _b, _c;
7268
+ const activeBaskets = enrichBasketsWithMetadata((_a = data === null || data === void 0 ? void 0 : data.active) !== null && _a !== void 0 ? _a : [], tokenMetadata);
7269
+ const topGainers = (() => {
7270
+ var _a;
7271
+ const list = (_a = data === null || data === void 0 ? void 0 : data.topGainers) !== null && _a !== void 0 ? _a : [];
7272
+ const limited = typeof topGainersLimit === 'number'
7273
+ ? list.slice(0, Math.max(0, topGainersLimit))
7274
+ : list;
7275
+ return enrichBasketsWithMetadata(limited, tokenMetadata);
7276
+ })();
7277
+ const topLosers = (() => {
7278
+ var _a;
7279
+ const list = (_a = data === null || data === void 0 ? void 0 : data.topLosers) !== null && _a !== void 0 ? _a : [];
7280
+ const limited = typeof topLosersLimit === 'number'
7281
+ ? list.slice(0, Math.max(0, topLosersLimit))
7282
+ : list;
7283
+ return enrichBasketsWithMetadata(limited, tokenMetadata);
7284
+ })();
7285
+ const highlightedBaskets = enrichBasketsWithMetadata((_b = data === null || data === void 0 ? void 0 : data.highlighted) !== null && _b !== void 0 ? _b : [], tokenMetadata);
7286
+ const watchlistBaskets = enrichBasketsWithMetadata((_c = data === null || data === void 0 ? void 0 : data.watchlist) !== null && _c !== void 0 ? _c : [], tokenMetadata);
7287
+ return {
7288
+ marketData: data,
7289
+ activeBaskets,
7290
+ topGainers,
7291
+ topLosers,
7292
+ highlightedBaskets,
7293
+ watchlistBaskets,
7294
+ };
7295
+ }, [data, tokenMetadata, topGainersLimit, topLosersLimit]);
7301
7296
  };
7302
7297
 
7303
7298
  async function toggleWatchlist(baseUrl, longAssets, shortAssets) {
@@ -7323,7 +7318,7 @@ function useWatchlist() {
7323
7318
  if (!context)
7324
7319
  throw new Error('useWatchlist must be used within a PearHyperliquidProvider');
7325
7320
  const { apiBaseUrl, isConnected } = context;
7326
- const marketData = useMarketDataPayload();
7321
+ const marketData = useMarketData(s => s.marketData);
7327
7322
  const isLoading = useMemo(() => !marketData && isConnected, [marketData, isConnected]);
7328
7323
  const toggle = async (longAssets, shortAssets) => {
7329
7324
  const resp = await toggleWatchlist(apiBaseUrl, longAssets, shortAssets);
@@ -7582,104 +7577,174 @@ function useAuth() {
7582
7577
 
7583
7578
  const useAllUserBalances = () => {
7584
7579
  const spotState = useUserData((state) => state.spotState);
7585
- const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
7586
- const rawClearinghouseStates = useHyperliquidData((state) => state.rawClearinghouseStates);
7587
7580
  const { longTokensMetadata, shortTokensMetadata } = useTokenSelectionMetadata();
7588
- return useMemo(() => {
7589
- const isLoading = !spotState || !aggregatedClearingHouseState;
7590
- // Helper function to truncate to 2 decimal places without rounding
7591
- const truncateToTwoDecimals = (value) => {
7592
- return Math.floor(value * 100) / 100;
7593
- };
7594
- // Get spot balances from spotState
7595
- let spotUsdcBal = undefined;
7596
- let spotUsdhBal = undefined;
7597
- if (spotState) {
7598
- const balances = spotState.balances || [];
7599
- for (const balance of balances) {
7581
+ const { longTokens, shortTokens } = useUserSelection$1();
7582
+ const aggregatedClearingHouseState = useHyperliquidData((state) => state.aggregatedClearingHouseState);
7583
+ const { spotBalances, availableToTrades, isLoading } = useMemo(() => {
7584
+ const isLoading = !spotState;
7585
+ const spotBalances = {};
7586
+ const availableToTrades = {};
7587
+ if (spotState === null || spotState === void 0 ? void 0 : spotState.balances) {
7588
+ for (const balance of spotState.balances) {
7600
7589
  const total = parseFloat(balance.total || '0');
7601
- if (balance.coin === 'USDC') {
7602
- spotUsdcBal = truncateToTwoDecimals(total);
7603
- }
7604
- if (balance.coin === 'USDH') {
7605
- spotUsdhBal = truncateToTwoDecimals(total);
7606
- }
7590
+ spotBalances[balance.coin] = total;
7607
7591
  }
7608
7592
  }
7609
- // Get available to trade from tokenMetadata for both USDC and USDH markets
7610
- let availableToTradeUsdcFromAsset = 0;
7611
- let availableToTradeUsdhFromAsset = 0;
7612
- // Token metadata only contains availableToTrade for SELECTED tokens (user's long and short Tokens)
7613
- // It does NOT contain data for all tokens, so we cannot reliably use it for available to trade as used on hl trade page
7614
- // so intead, we rely on rawClearinghouseStates which provides market-specific data
7615
- const selectedMetadataEntries = [
7616
- ...Object.entries(longTokensMetadata),
7617
- ...Object.entries(shortTokensMetadata),
7593
+ const allMetadataWithSource = [
7594
+ ...Object.values(longTokensMetadata).map((m) => ({
7595
+ metadata: m,
7596
+ isLong: true,
7597
+ })),
7598
+ ...Object.values(shortTokensMetadata).map((m) => ({
7599
+ metadata: m,
7600
+ isLong: false,
7601
+ })),
7618
7602
  ];
7619
- selectedMetadataEntries.forEach(([symbol, metadata]) => {
7620
- var _a;
7621
- const availableStr = (_a = metadata === null || metadata === void 0 ? void 0 : metadata.availableToTrade) === null || _a === void 0 ? void 0 : _a[0];
7622
- if (!availableStr)
7623
- return;
7624
- const availableValue = truncateToTwoDecimals(parseFloat(availableStr || '0'));
7625
- if ((metadata === null || metadata === void 0 ? void 0 : metadata.collateralToken) === 'USDH') {
7626
- availableToTradeUsdhFromAsset = Math.max(availableToTradeUsdhFromAsset, availableValue);
7627
- return;
7628
- }
7629
- if ((metadata === null || metadata === void 0 ? void 0 : metadata.collateralToken) === 'USDC') {
7630
- availableToTradeUsdcFromAsset = Math.max(availableToTradeUsdcFromAsset, availableValue);
7631
- return;
7632
- }
7633
- if (symbol.includes(':')) {
7634
- const prefix = symbol.split(':')[0];
7635
- if (prefix === 'xyz') {
7636
- availableToTradeUsdcFromAsset = Math.max(availableToTradeUsdcFromAsset, availableValue);
7637
- }
7638
- else {
7639
- availableToTradeUsdhFromAsset = Math.max(availableToTradeUsdhFromAsset, availableValue);
7640
- }
7641
- return;
7603
+ for (const { metadata, isLong } of allMetadataWithSource) {
7604
+ if (!(metadata === null || metadata === void 0 ? void 0 : metadata.collateralToken) || !(metadata === null || metadata === void 0 ? void 0 : metadata.availableToTrade))
7605
+ continue;
7606
+ const collateralCoin = metadata.collateralToken;
7607
+ const availableToTrade = metadata.availableToTrade;
7608
+ let value = parseFloat(availableToTrade[isLong ? 0 : 1] || '0');
7609
+ if (!(collateralCoin in availableToTrades) ||
7610
+ value < availableToTrades[collateralCoin]) {
7611
+ availableToTrades[collateralCoin] = value;
7642
7612
  }
7643
- availableToTradeUsdcFromAsset = Math.max(availableToTradeUsdcFromAsset, availableValue);
7644
- });
7645
- // Calculate USDC available to trade
7646
- // Priority 1: Use value from activeAssetData if available (> 0)
7647
- // Priority 2: Calculate from USDC-specific clearinghouseState (empty prefix)
7648
- let availableToTradeUsdcValue = undefined;
7649
- if (availableToTradeUsdcFromAsset > 0) {
7650
- availableToTradeUsdcValue = availableToTradeUsdcFromAsset;
7651
7613
  }
7652
- else if (rawClearinghouseStates) {
7653
- // Find USDC market (empty prefix)
7654
- const usdcMarket = rawClearinghouseStates.find(([prefix]) => prefix === '');
7655
- const usdcState = usdcMarket === null || usdcMarket === void 0 ? void 0 : usdcMarket[1];
7656
- if (usdcState === null || usdcState === void 0 ? void 0 : usdcState.marginSummary) {
7657
- const accountValue = parseFloat(usdcState.marginSummary.accountValue || '0');
7658
- const totalMarginUsed = parseFloat(usdcState.marginSummary.totalMarginUsed || '0');
7659
- const calculatedValue = Math.max(0, accountValue - totalMarginUsed);
7660
- availableToTradeUsdcValue = truncateToTwoDecimals(calculatedValue);
7661
- }
7614
+ if (!availableToTrades['USDC']) {
7615
+ availableToTrades['USDC'] = parseFloat((aggregatedClearingHouseState === null || aggregatedClearingHouseState === void 0 ? void 0 : aggregatedClearingHouseState.marginSummary.totalRawUsd) || '0');
7662
7616
  }
7663
- // Calculate USDH available to trade
7664
- // Priority 1: Use value from activeAssetData if available (> 0)
7665
- // Priority 2: Use spot USDH balance
7666
- const availableToTradeUsdhValue = availableToTradeUsdhFromAsset > 0
7667
- ? availableToTradeUsdhFromAsset
7668
- : spotUsdhBal;
7669
7617
  return {
7670
- spotUsdcBalance: spotUsdcBal,
7671
- availableToTradeUsdc: availableToTradeUsdcValue,
7672
- spotUsdhBalance: spotUsdhBal,
7673
- availableToTradeUsdh: availableToTradeUsdhValue,
7618
+ spotBalances,
7619
+ availableToTrades,
7674
7620
  isLoading,
7675
7621
  };
7622
+ }, [spotState, longTokensMetadata, shortTokensMetadata, aggregatedClearingHouseState]);
7623
+ /**
7624
+ * Calculate margin required for every collateral token based on asset leverages and size.
7625
+ * Returns margin required per collateral and whether there's sufficient margin.
7626
+ */
7627
+ const getMarginRequired = useCallback((assetsLeverage, size) => {
7628
+ const sizeValue = parseFloat(String(size)) || 0;
7629
+ // Group tokens by collateral type and calculate margin required
7630
+ const marginByCollateral = {};
7631
+ // Process all tokens (long and short)
7632
+ const allTokensWithMetadata = [
7633
+ ...longTokens.map((t) => ({
7634
+ ...t,
7635
+ metadata: longTokensMetadata[t.symbol],
7636
+ })),
7637
+ ...shortTokens.map((t) => ({
7638
+ ...t,
7639
+ metadata: shortTokensMetadata[t.symbol],
7640
+ })),
7641
+ ];
7642
+ let totalMarginRequired = 0;
7643
+ allTokensWithMetadata.forEach((token) => {
7644
+ var _a;
7645
+ const weight = token.weight || 0;
7646
+ const assetSize = (sizeValue * weight) / 100;
7647
+ const assetLeverage = assetsLeverage[token.symbol] || 1;
7648
+ const assetMargin = assetLeverage > 0 ? assetSize / assetLeverage : 0;
7649
+ // Get collateral type from metadata, default to USDC
7650
+ const collateralToken = ((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.collateralToken) || 'USDC';
7651
+ if (!marginByCollateral[collateralToken]) {
7652
+ marginByCollateral[collateralToken] = 0;
7653
+ }
7654
+ marginByCollateral[collateralToken] += assetMargin;
7655
+ totalMarginRequired += assetMargin;
7656
+ });
7657
+ const perCollateral = Object.entries(marginByCollateral).map(([collateral, marginRequired]) => {
7658
+ var _a;
7659
+ const collateralToken = collateral;
7660
+ const availableBalance = (_a = availableToTrades[collateralToken]) !== null && _a !== void 0 ? _a : 0;
7661
+ const hasEnough = marginRequired <= availableBalance;
7662
+ const shortfall = Math.max(0, marginRequired - availableBalance);
7663
+ return {
7664
+ collateral: collateralToken,
7665
+ marginRequired: Number(marginRequired.toFixed(2)),
7666
+ availableBalance: Number(availableBalance.toFixed(2)),
7667
+ hasEnough,
7668
+ shortfall: Number(shortfall.toFixed(2)),
7669
+ };
7670
+ });
7671
+ const hasEnoughTotal = perCollateral.every((c) => c.hasEnough);
7672
+ return {
7673
+ totalMarginRequired: Number(totalMarginRequired.toFixed(2)),
7674
+ orderValue: sizeValue,
7675
+ perCollateral,
7676
+ hasEnoughTotal,
7677
+ };
7676
7678
  }, [
7677
- spotState,
7678
- aggregatedClearingHouseState,
7679
- rawClearinghouseStates,
7679
+ longTokens,
7680
+ shortTokens,
7680
7681
  longTokensMetadata,
7681
7682
  shortTokensMetadata,
7683
+ spotBalances,
7684
+ availableToTrades,
7682
7685
  ]);
7686
+ /**
7687
+ * Calculate the maximum order size ($) based on available balances and asset leverages.
7688
+ * Returns the overall maximum order size constrained by all collateral types.
7689
+ */
7690
+ const getMaxSize = useCallback((assetsLeverage) => {
7691
+ const marginFactorByCollateral = {};
7692
+ const allTokensWithMetadata = [
7693
+ ...longTokens.map((t) => ({
7694
+ ...t,
7695
+ metadata: longTokensMetadata[t.symbol],
7696
+ })),
7697
+ ...shortTokens.map((t) => ({
7698
+ ...t,
7699
+ metadata: shortTokensMetadata[t.symbol],
7700
+ })),
7701
+ ];
7702
+ // Calculate the margin factor for each collateral type
7703
+ // marginFactor = sum of (weight / leverage) for all assets using that collateral
7704
+ allTokensWithMetadata.forEach((token) => {
7705
+ var _a;
7706
+ const weight = token.weight || 0;
7707
+ const assetLeverage = assetsLeverage[token.symbol] || 1;
7708
+ const collateralToken = ((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.collateralToken) || 'USDC';
7709
+ // marginFactor represents how much margin is needed per $1 of order size
7710
+ const marginFactor = assetLeverage > 0 ? weight / (100 * assetLeverage) : 0;
7711
+ if (!marginFactorByCollateral[collateralToken]) {
7712
+ marginFactorByCollateral[collateralToken] = 0;
7713
+ }
7714
+ marginFactorByCollateral[collateralToken] += marginFactor;
7715
+ });
7716
+ // Calculate max size for each collateral type
7717
+ // maxSize = availableBalance / marginFactor
7718
+ // Each maxSize represents the total order size that collateral can support
7719
+ const maxSizePerCollateral = [];
7720
+ Object.entries(marginFactorByCollateral).forEach(([collateral, marginFactor]) => {
7721
+ var _a;
7722
+ const collateralToken = collateral;
7723
+ const availableBalance = (_a = availableToTrades[collateralToken]) !== null && _a !== void 0 ? _a : 0;
7724
+ if (marginFactor > 0) {
7725
+ const maxSize = availableBalance / marginFactor;
7726
+ maxSizePerCollateral.push(maxSize);
7727
+ }
7728
+ });
7729
+ if (maxSizePerCollateral.length === 0) {
7730
+ return 0;
7731
+ }
7732
+ const overallMaxSize = Math.min(...maxSizePerCollateral);
7733
+ return Number(overallMaxSize.toFixed(2));
7734
+ }, [
7735
+ longTokens,
7736
+ shortTokens,
7737
+ longTokensMetadata,
7738
+ shortTokensMetadata,
7739
+ availableToTrades,
7740
+ ]);
7741
+ return {
7742
+ spotBalances,
7743
+ availableToTrades,
7744
+ isLoading,
7745
+ getMarginRequired,
7746
+ getMaxSize,
7747
+ };
7683
7748
  };
7684
7749
 
7685
7750
  /**
@@ -7821,9 +7886,10 @@ const PearHyperliquidContext = createContext(undefined);
7821
7886
  const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearprotocol.io', clientId = 'PEARPROTOCOLUI', wsUrl = 'wss://hl-ui.pearprotocol.io/ws', }) => {
7822
7887
  const address = useUserData((s) => s.address);
7823
7888
  const setAddress = useUserData((s) => s.setAddress);
7824
- const perpsMetaAssets = useHyperliquidData((state) => state.perpMetaAssets);
7825
- const setPerpMetaAssets = useHyperliquidData((state) => state.setPerpMetaAssets);
7826
- const websocketsEnabled = useMemo(() => Array.isArray(perpsMetaAssets) && perpsMetaAssets.length > 0, [perpsMetaAssets]);
7889
+ const perpMetasByDex = useHyperliquidData((state) => state.perpMetasByDex);
7890
+ const setPerpDexs = useHyperliquidData((state) => state.setPerpDexs);
7891
+ const setPerpMetasByDex = useHyperliquidData((state) => state.setPerpMetasByDex);
7892
+ const websocketsEnabled = useMemo(() => perpMetasByDex !== null && perpMetasByDex.size > 0, [perpMetasByDex]);
7827
7893
  const { handleUserFillsEvent } = useHyperliquidUserFills({
7828
7894
  baseUrl: apiBaseUrl,
7829
7895
  address,
@@ -7840,15 +7906,18 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
7840
7906
  onUserFills: handleUserFillsEvent,
7841
7907
  });
7842
7908
  useEffect(() => {
7843
- if (perpsMetaAssets === null) {
7844
- fetchAllPerpMetas()
7845
- .then((res) => {
7846
- const allPerpMetas = [];
7847
- res.data.forEach((item) => {
7848
- // Only include USDC and USDH collateral tokens
7849
- if (item.collateralToken !== 360 && item.collateralToken !== 0) {
7850
- return;
7851
- }
7909
+ if (perpMetasByDex === null) {
7910
+ Promise.all([fetchPerpDexs(), fetchAllPerpMetas()])
7911
+ .then(([perpDexsRes, perpMetasRes]) => {
7912
+ const perpDexs = perpDexsRes.data;
7913
+ const perpMetas = perpMetasRes.data;
7914
+ setPerpDexs(perpDexs);
7915
+ const metasByDex = new Map();
7916
+ perpMetas.forEach((item, perpIndex) => {
7917
+ var _a, _b;
7918
+ const dexName = perpIndex === 0
7919
+ ? 'HYPERLIQUID'
7920
+ : ((_b = (_a = perpDexs[perpIndex]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : `DEX_${perpIndex}`);
7852
7921
  var collateralToken;
7853
7922
  if (item.collateralToken === 360) {
7854
7923
  collateralToken = 'USDH';
@@ -7856,22 +7925,23 @@ const PearHyperliquidProvider = ({ children, apiBaseUrl = 'https://hl-ui.pearpro
7856
7925
  if (item.collateralToken === 0) {
7857
7926
  collateralToken = 'USDC';
7858
7927
  }
7859
- item.universe.forEach((asset) => {
7860
- const assetWithMeta = {
7861
- ...asset,
7862
- collateralToken,
7863
- };
7864
- allPerpMetas.push(assetWithMeta);
7865
- });
7928
+ if (item.collateralToken === 235) {
7929
+ collateralToken = 'USDE';
7930
+ }
7931
+ if (item.collateralToken === 268) {
7932
+ collateralToken = 'USDT0';
7933
+ }
7934
+ const universeAssets = item.universe.map((asset) => ({
7935
+ ...asset,
7936
+ collateralToken,
7937
+ }));
7938
+ metasByDex.set(dexName, universeAssets);
7866
7939
  });
7867
- setPerpMetaAssets(allPerpMetas);
7940
+ setPerpMetasByDex(metasByDex);
7868
7941
  })
7869
7942
  .catch(() => { });
7870
7943
  }
7871
- }, [
7872
- perpsMetaAssets,
7873
- setPerpMetaAssets,
7874
- ]);
7944
+ }, [perpMetasByDex, setPerpDexs, setPerpMetasByDex]);
7875
7945
  const contextValue = useMemo(() => ({
7876
7946
  // Config
7877
7947
  clientId,
@@ -8155,4 +8225,4 @@ function getOrderTrailingInfo(order) {
8155
8225
  return undefined;
8156
8226
  }
8157
8227
 
8158
- export { AccountSummaryCalculator, ConflictDetector, MAX_ASSETS_PER_LEG, MINIMUM_ASSET_USD_VALUE, MaxAssetsPerLegError, MinimumPositionSizeError, PearHyperliquidProvider, TokenMetadataExtractor, adjustAdvancePosition, adjustOrder, adjustPosition, calculateMinimumPositionValue, calculateWeightedRatio, cancelOrder, cancelTwap, cancelTwapOrder, closeAllPositions, closePosition, computeBasketCandles, createCandleLookups, createPosition, executeSpotOrder, getAssetByName, getCompleteTimestamps, getKalshiMarkets, getOrderDirection, getOrderLadderConfig, getOrderLeverage, getOrderReduceOnly, getOrderTpSlTriggerType, getOrderTrailingInfo, getOrderTriggerType, getOrderTriggerValue, getOrderTwapDuration, getOrderUsdValue, getPortfolio, isBtcDomOrder, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, selectTokenMetadataBySymbols, toggleWatchlist, updateLeverage, updateRiskParameters, useAccountSummary, useActiveBaskets, useAgentWallet, useAllUserBalances, useAuth, useBasketCandles, useFindBasket, useHighlightedBaskets, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidNativeWebSocket, useHyperliquidUserFills, useHyperliquidWebSocket, useMarket, useMarketData, useMarketDataPayload, useNotifications, useOpenOrders, useOrders, usePearHyperliquid, usePerformanceOverlays, usePortfolio, usePosition, useSpotOrder, useTokenSelectionMetadata, useTopGainers, useTopLosers, useTradeHistories, useTwap, useUserSelection, useWatchlist, useWatchlistBaskets, validateMaxAssetsPerLeg, validateMinimumAssetSize, validatePositionSize };
8228
+ export { ConflictDetector, MAX_ASSETS_PER_LEG, MINIMUM_ASSET_USD_VALUE, MaxAssetsPerLegError, MinimumPositionSizeError, PearHyperliquidProvider, adjustAdvancePosition, adjustOrder, adjustPosition, calculateMinimumPositionValue, calculateWeightedRatio, cancelOrder, cancelTwap, cancelTwapOrder, closeAllPositions, closePosition, computeBasketCandles, createCandleLookups, createPosition, executeSpotOrder, getCompleteTimestamps, getKalshiMarkets, getOrderDirection, getOrderLadderConfig, getOrderLeverage, getOrderReduceOnly, getOrderTpSlTriggerType, getOrderTrailingInfo, getOrderTriggerType, getOrderTriggerValue, getOrderTwapDuration, getOrderUsdValue, getPortfolio, isBtcDomOrder, mapCandleIntervalToTradingViewInterval, mapTradingViewIntervalToCandleInterval, markNotificationReadById, markNotificationsRead, toggleWatchlist, updateLeverage, updateRiskParameters, useAccountSummary, useAgentWallet, useAllUserBalances, useAuth, useBasketCandles, useHistoricalPriceData, useHistoricalPriceDataStore, useHyperliquidUserFills, useMarket, useMarketData, useMarketDataHook, useNotifications, useOpenOrders, useOrders, usePearHyperliquid, usePerformanceOverlays, usePortfolio, usePosition, useSpotOrder, useTokenSelectionMetadata, useTradeHistories, useTwap, useUserSelection, useWatchlist, validateMaxAssetsPerLeg, validateMinimumAssetSize, validatePositionSize };