@metamask-previews/perps-controller 12.2.0-preview-e68ab1498 → 13.0.0-preview-4b37525

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +15 -6
  2. package/dist/PerpsController-method-action-types.cjs.map +1 -1
  3. package/dist/PerpsController-method-action-types.d.cts +26 -3
  4. package/dist/PerpsController-method-action-types.d.cts.map +1 -1
  5. package/dist/PerpsController-method-action-types.d.mts +26 -3
  6. package/dist/PerpsController-method-action-types.d.mts.map +1 -1
  7. package/dist/PerpsController-method-action-types.mjs.map +1 -1
  8. package/dist/PerpsController.cjs +392 -102
  9. package/dist/PerpsController.cjs.map +1 -1
  10. package/dist/PerpsController.d.cts +26 -7
  11. package/dist/PerpsController.d.cts.map +1 -1
  12. package/dist/PerpsController.d.mts +26 -7
  13. package/dist/PerpsController.d.mts.map +1 -1
  14. package/dist/PerpsController.mjs +392 -102
  15. package/dist/PerpsController.mjs.map +1 -1
  16. package/dist/constants/eventNames.cjs +1 -1
  17. package/dist/constants/eventNames.cjs.map +1 -1
  18. package/dist/constants/eventNames.d.cts +1 -1
  19. package/dist/constants/eventNames.d.mts +1 -1
  20. package/dist/constants/eventNames.mjs +1 -1
  21. package/dist/constants/eventNames.mjs.map +1 -1
  22. package/dist/constants/hyperLiquidConfig.cjs +14 -2
  23. package/dist/constants/hyperLiquidConfig.cjs.map +1 -1
  24. package/dist/constants/hyperLiquidConfig.d.cts +10 -0
  25. package/dist/constants/hyperLiquidConfig.d.cts.map +1 -1
  26. package/dist/constants/hyperLiquidConfig.d.mts +10 -0
  27. package/dist/constants/hyperLiquidConfig.d.mts.map +1 -1
  28. package/dist/constants/hyperLiquidConfig.mjs +12 -0
  29. package/dist/constants/hyperLiquidConfig.mjs.map +1 -1
  30. package/dist/constants/perpsConfig.cjs +3 -4
  31. package/dist/constants/perpsConfig.cjs.map +1 -1
  32. package/dist/constants/perpsConfig.d.cts +2 -4
  33. package/dist/constants/perpsConfig.d.cts.map +1 -1
  34. package/dist/constants/perpsConfig.d.mts +2 -4
  35. package/dist/constants/perpsConfig.d.mts.map +1 -1
  36. package/dist/constants/perpsConfig.mjs +3 -4
  37. package/dist/constants/perpsConfig.mjs.map +1 -1
  38. package/dist/index.cjs +8 -6
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +4 -3
  41. package/dist/index.d.cts.map +1 -1
  42. package/dist/index.d.mts +4 -3
  43. package/dist/index.d.mts.map +1 -1
  44. package/dist/index.mjs +1 -0
  45. package/dist/index.mjs.map +1 -1
  46. package/dist/perpsErrorCodes.cjs +5 -1
  47. package/dist/perpsErrorCodes.cjs.map +1 -1
  48. package/dist/perpsErrorCodes.d.cts +4 -0
  49. package/dist/perpsErrorCodes.d.cts.map +1 -1
  50. package/dist/perpsErrorCodes.d.mts +4 -0
  51. package/dist/perpsErrorCodes.d.mts.map +1 -1
  52. package/dist/perpsErrorCodes.mjs +5 -1
  53. package/dist/perpsErrorCodes.mjs.map +1 -1
  54. package/dist/providers/AggregatedPerpsProvider.cjs +104 -14
  55. package/dist/providers/AggregatedPerpsProvider.cjs.map +1 -1
  56. package/dist/providers/AggregatedPerpsProvider.d.cts +34 -2
  57. package/dist/providers/AggregatedPerpsProvider.d.cts.map +1 -1
  58. package/dist/providers/AggregatedPerpsProvider.d.mts +34 -2
  59. package/dist/providers/AggregatedPerpsProvider.d.mts.map +1 -1
  60. package/dist/providers/AggregatedPerpsProvider.mjs +102 -13
  61. package/dist/providers/AggregatedPerpsProvider.mjs.map +1 -1
  62. package/dist/providers/HyperLiquidProvider.cjs +2141 -637
  63. package/dist/providers/HyperLiquidProvider.cjs.map +1 -1
  64. package/dist/providers/HyperLiquidProvider.d.cts +37 -17
  65. package/dist/providers/HyperLiquidProvider.d.cts.map +1 -1
  66. package/dist/providers/HyperLiquidProvider.d.mts +37 -17
  67. package/dist/providers/HyperLiquidProvider.d.mts.map +1 -1
  68. package/dist/providers/HyperLiquidProvider.mjs +2146 -642
  69. package/dist/providers/HyperLiquidProvider.mjs.map +1 -1
  70. package/dist/types/index.cjs.map +1 -1
  71. package/dist/types/index.d.cts +96 -3
  72. package/dist/types/index.d.cts.map +1 -1
  73. package/dist/types/index.d.mts +96 -3
  74. package/dist/types/index.d.mts.map +1 -1
  75. package/dist/types/index.mjs.map +1 -1
  76. package/dist/utils/capabilitySymbols.cjs +25 -0
  77. package/dist/utils/capabilitySymbols.cjs.map +1 -0
  78. package/dist/utils/capabilitySymbols.d.cts +15 -0
  79. package/dist/utils/capabilitySymbols.d.cts.map +1 -0
  80. package/dist/utils/capabilitySymbols.d.mts +15 -0
  81. package/dist/utils/capabilitySymbols.d.mts.map +1 -0
  82. package/dist/utils/capabilitySymbols.mjs +21 -0
  83. package/dist/utils/capabilitySymbols.mjs.map +1 -0
  84. package/dist/utils/hyperLiquidAdapter.cjs +20 -1
  85. package/dist/utils/hyperLiquidAdapter.cjs.map +1 -1
  86. package/dist/utils/hyperLiquidAdapter.d.cts +1 -0
  87. package/dist/utils/hyperLiquidAdapter.d.cts.map +1 -1
  88. package/dist/utils/hyperLiquidAdapter.d.mts +1 -0
  89. package/dist/utils/hyperLiquidAdapter.d.mts.map +1 -1
  90. package/dist/utils/hyperLiquidAdapter.mjs +19 -0
  91. package/dist/utils/hyperLiquidAdapter.mjs.map +1 -1
  92. package/dist/utils/hyperLiquidValidation.cjs +1 -1
  93. package/dist/utils/hyperLiquidValidation.cjs.map +1 -1
  94. package/dist/utils/hyperLiquidValidation.d.cts +1 -1
  95. package/dist/utils/hyperLiquidValidation.d.mts +1 -1
  96. package/dist/utils/hyperLiquidValidation.mjs +1 -1
  97. package/dist/utils/hyperLiquidValidation.mjs.map +1 -1
  98. package/dist/utils/stringParseUtils.cjs +17 -1
  99. package/dist/utils/stringParseUtils.cjs.map +1 -1
  100. package/dist/utils/stringParseUtils.d.cts +8 -0
  101. package/dist/utils/stringParseUtils.d.cts.map +1 -1
  102. package/dist/utils/stringParseUtils.d.mts +8 -0
  103. package/dist/utils/stringParseUtils.d.mts.map +1 -1
  104. package/dist/utils/stringParseUtils.mjs +15 -0
  105. package/dist/utils/stringParseUtils.mjs.map +1 -1
  106. package/package.json +1 -1
@@ -9,12 +9,13 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
9
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _HyperLiquidProvider_instances, _HyperLiquidProvider_deps, _HyperLiquidProvider_clientService, _HyperLiquidProvider_walletService, _HyperLiquidProvider_subscriptionService, _HyperLiquidProvider_symbolToAssetId, _HyperLiquidProvider_userFeeCache, _HyperLiquidProvider_maxLeverageCache, _HyperLiquidProvider_cachedMetaByDex, _HyperLiquidProvider_cachedMarketDataWithPrices, _HyperLiquidProvider_cachedSpotMeta, _HyperLiquidProvider_dexDiscoveryCache, _HyperLiquidProvider_referralCheckCache, _HyperLiquidProvider_builderFeeCheckCache, _HyperLiquidProvider_ensureReadyPromise, _HyperLiquidProvider_pendingBuilderFeeApprovals, _HyperLiquidProvider_subscriptionBuilderApprovalEpoch, _HyperLiquidProvider_approvedBuilderAddresses, _HyperLiquidProvider_compiledAllowlistPatterns, _HyperLiquidProvider_compiledBlocklistPatterns, _HyperLiquidProvider_userFeeDiscountBips, _HyperLiquidProvider_userFeeResolution, _HyperLiquidProvider_hip3Enabled, _HyperLiquidProvider_allowlistMarkets, _HyperLiquidProvider_blocklistMarkets, _HyperLiquidProvider_useUnifiedAccount, _HyperLiquidProvider_dexDiscoveryComplete, _HyperLiquidProvider_unifiedAccountSetupNeedsRetry, _HyperLiquidProvider_pendingValidatedDexsPromise, _HyperLiquidProvider_cachedUsdcTokenId, _HyperLiquidProvider_errorMappings, _HyperLiquidProvider_scaleOrderGroups, _HyperLiquidProvider_chaseSessions, _HyperLiquidProvider_chaseTerminations, _HyperLiquidProvider_terminatedChaseHandles, _HyperLiquidProvider_chasePlacementsInFlight, _HyperLiquidProvider_chasePlacementWaiters, _HyperLiquidProvider_chaseTickQueue, _HyperLiquidProvider_chaseGeneration, _HyperLiquidProvider_chasePlacementBlockers, _HyperLiquidProvider_clientsInitialized, _HyperLiquidProvider_initializationPromise, _HyperLiquidProvider_messenger, _HyperLiquidProvider_builderAddressTestnet, _HyperLiquidProvider_builderAddressMainnet, _HyperLiquidProvider_subscriptionBuilderAddressTestnet, _HyperLiquidProvider_subscriptionBuilderAddressMainnet, _HyperLiquidProvider_priceDeviationLimit, _HyperLiquidProvider_compilePatternsSafely, _HyperLiquidProvider_ensureClientsInitialized, _HyperLiquidProvider_isWalletOnHyperliquid, _HyperLiquidProvider_isHyperliquidMultiSigAccount, _HyperLiquidProvider_ensureUnifiedAccountEnabled, _HyperLiquidProvider_ensureReady, _HyperLiquidProvider_tradingSetupPromise, _HyperLiquidProvider_tradingSetupComplete, _HyperLiquidProvider_ensureReadyForTrading, _HyperLiquidProvider_getOrFetchPrice, _HyperLiquidProvider_filterFills, _HyperLiquidProvider_getAllAvailableDexs, _HyperLiquidProvider_getValidatedDexs, _HyperLiquidProvider_fetchValidatedDexsInternal, _HyperLiquidProvider_getCachedMeta, _HyperLiquidProvider_backfillAssetMapForDex, _HyperLiquidProvider_getAssetIdWithRepair, _HyperLiquidProvider_getCachedSpotMeta, _HyperLiquidProvider_getCachedPerpDexs, _HyperLiquidProvider_calculateHip3FeeMultiplier, _HyperLiquidProvider_getCacheKey, _HyperLiquidProvider_getApprovedBuilderKey, _HyperLiquidProvider_fetchMarketsForDex, _HyperLiquidProvider_getUsdcTokenId, _HyperLiquidProvider_isUsdcCollateralDex, _HyperLiquidProvider_buildAssetMapping, _HyperLiquidProvider_queryUserDataAcrossDexs, _HyperLiquidProvider_mapError, _HyperLiquidProvider_getErrorContext, _HyperLiquidProvider_isMappedAccountModeExchangeError, _HyperLiquidProvider_getTradingErrorContext, _HyperLiquidProvider_checkBuilderFeeApproval, _HyperLiquidProvider_ensureBuilderFeeApproval, _HyperLiquidProvider_checkBuilderFeeStatus, _HyperLiquidProvider_getBalanceForDex, _HyperLiquidProvider_findSourceDexWithBalance, _HyperLiquidProvider_autoTransferForHip3Order, _HyperLiquidProvider_autoTransferBackAfterClose, _HyperLiquidProvider_calculateHip3RequiredMargin, _HyperLiquidProvider_handleHip3PostOrderRebalance, _HyperLiquidProvider_handleHip3OrderRollback, _HyperLiquidProvider_validateOrderBeforePlacement, _HyperLiquidProvider_getAssetInfo, _HyperLiquidProvider_prepareAssetForTrading, _HyperLiquidProvider_handleHip3PreOrder, _HyperLiquidProvider_submitOrderWithRollback, _HyperLiquidProvider_handleOrderError, _HyperLiquidProvider_placeStrategyOrder, _HyperLiquidProvider_prepareStrategyPlacement, _HyperLiquidProvider_getMinimumOrderSize, _HyperLiquidProvider_buildScaleLadder, _HyperLiquidProvider_placeTwapOrder, _HyperLiquidProvider_placeScaleOrder, _HyperLiquidProvider_startChaseSession, _HyperLiquidProvider_getChaseQuotePrice, _HyperLiquidProvider_restChaseOrder, _HyperLiquidProvider_scheduleChaseTick, _HyperLiquidProvider_pauseChaseTicksForPlacement, _HyperLiquidProvider_resumeChaseTicksAfterPlacement, _HyperLiquidProvider_recoverFailedChaseTick, _HyperLiquidProvider_runChaseTick, _HyperLiquidProvider_resolveOwnRestingSizes, _HyperLiquidProvider_readOrderRemainder, _HyperLiquidProvider_cancelChaseChild, _HyperLiquidProvider_retractOrphanedChaseOrder, _HyperLiquidProvider_stopChaseSession, _HyperLiquidProvider_cancelStrategyOrder, _HyperLiquidProvider_cancelTwapOrder, _HyperLiquidProvider_cancelScaleOrder, _HyperLiquidProvider_cancelChaseOrder, _HyperLiquidProvider_performChaseTermination, _HyperLiquidProvider_readOrderIdFromStatus, _HyperLiquidProvider_getDiscountedBuilderFee, _HyperLiquidProvider_getBuilderOrderContext, _HyperLiquidProvider_fetchOpenOrders, _HyperLiquidProvider_resolveReplacementOrderId, _HyperLiquidProvider_getStandaloneValidatedDexs, _HyperLiquidProvider_queryDexPositions, _HyperLiquidProvider_getAllMids, _HyperLiquidProvider_fetchSingleDexFresh, _HyperLiquidProvider_excludeNonUsdcCollateralResults, _HyperLiquidProvider_mergeDexResultsInto, _HyperLiquidProvider_cacheFreshMarketDataSnapshot, _HyperLiquidProvider_getStaleMarketDataSnapshot, _HyperLiquidProvider_isFeeCacheValid, _HyperLiquidProvider_getBuilderAddress, _HyperLiquidProvider_getSubscriptionBuilderAddress, _HyperLiquidProvider_getReferralCode, _HyperLiquidProvider_ensureReferralSet, _HyperLiquidProvider_isReferralCodeReady, _HyperLiquidProvider_checkReferralSet, _HyperLiquidProvider_setReferralCode;
13
- import { hasProperty } from "@metamask/utils";
12
+ var _HyperLiquidProvider_instances, _a, _HyperLiquidProvider_deps, _HyperLiquidProvider_clientService, _HyperLiquidProvider_walletService, _HyperLiquidProvider_subscriptionService, _HyperLiquidProvider_symbolToAssetId, _HyperLiquidProvider_userFeeCache, _HyperLiquidProvider_maxLeverageCache, _HyperLiquidProvider_cachedMetaByDex, _HyperLiquidProvider_orderCapabilitiesMarketsByDex, _HyperLiquidProvider_orderCapabilitiesRefreshByDex, _HyperLiquidProvider_isDisconnected, _HyperLiquidProvider_disconnectOperationsInFlight, _HyperLiquidProvider_disconnectOperationPromise, _HyperLiquidProvider_lifecycleGeneration, _HyperLiquidProvider_cachedMarketDataWithPrices, _HyperLiquidProvider_cachedSpotMeta, _HyperLiquidProvider_dexDiscoveryCache, _HyperLiquidProvider_referralCheckCache, _HyperLiquidProvider_builderFeeCheckCache, _HyperLiquidProvider_ensureReadyPromise, _HyperLiquidProvider_pendingBuilderFeeApprovals, _HyperLiquidProvider_subscriptionBuilderApprovalEpoch, _HyperLiquidProvider_approvedBuilderAddresses, _HyperLiquidProvider_compiledAllowlistPatterns, _HyperLiquidProvider_compiledBlocklistPatterns, _HyperLiquidProvider_userFeeDiscountBips, _HyperLiquidProvider_userFeeResolution, _HyperLiquidProvider_hip3Enabled, _HyperLiquidProvider_allowlistMarkets, _HyperLiquidProvider_blocklistMarkets, _HyperLiquidProvider_useUnifiedAccount, _HyperLiquidProvider_dexDiscoveryComplete, _HyperLiquidProvider_unifiedAccountSetupNeedsRetry, _HyperLiquidProvider_pendingValidatedDexsPromise, _HyperLiquidProvider_cachedUsdcTokenId, _HyperLiquidProvider_errorMappings, _HyperLiquidProvider_scaleOrderGroups, _HyperLiquidProvider_cancelledScaleOrderGroups, _HyperLiquidProvider_trackedTwapOrdersByMessenger, _HyperLiquidProvider_trackedTwapOrders, _HyperLiquidProvider_chaseSessions, _HyperLiquidProvider_chaseTerminations, _HyperLiquidProvider_terminatedChaseHandles, _HyperLiquidProvider_chasePlacementsInFlight, _HyperLiquidProvider_chasePlacementWaiters, _HyperLiquidProvider_chaseTickQueue, _HyperLiquidProvider_strategyGeneration, _HyperLiquidProvider_chasePlacementBlockers, _HyperLiquidProvider_clientsInitialized, _HyperLiquidProvider_initializationPromise, _HyperLiquidProvider_messenger, _HyperLiquidProvider_builderAddressTestnet, _HyperLiquidProvider_builderAddressMainnet, _HyperLiquidProvider_subscriptionBuilderAddressTestnet, _HyperLiquidProvider_subscriptionBuilderAddressMainnet, _HyperLiquidProvider_priceDeviationLimit, _HyperLiquidProvider_onChaseOrderMaxDistanceReached, _HyperLiquidProvider_resolveOrderFeePolicy, _HyperLiquidProvider_assertProviderLifecycleCurrent, _HyperLiquidProvider_assertCacheWriteLifecycleCurrent, _HyperLiquidProvider_isCacheWriteLifecycleCurrent, _HyperLiquidProvider_getFreshOrderCapabilityMarkets, _HyperLiquidProvider_compilePatternsSafely, _HyperLiquidProvider_ensureClientsInitialized, _HyperLiquidProvider_isWalletOnHyperliquid, _HyperLiquidProvider_isHyperliquidMultiSigAccount, _HyperLiquidProvider_ensureUnifiedAccountEnabled, _HyperLiquidProvider_ensureReady, _HyperLiquidProvider_tradingSetupPromise, _HyperLiquidProvider_tradingSetupComplete, _HyperLiquidProvider_builderFeeSetupPromises, _HyperLiquidProvider_ensureBuilderFeeSetup, _HyperLiquidProvider_ensureReadyForTrading, _HyperLiquidProvider_getOrFetchPrice, _HyperLiquidProvider_filterFills, _HyperLiquidProvider_getAllAvailableDexs, _HyperLiquidProvider_getValidatedDexs, _HyperLiquidProvider_fetchValidatedDexsInternal, _HyperLiquidProvider_getCachedMeta, _HyperLiquidProvider_fetchMeta, _HyperLiquidProvider_cacheDexMetadata, _HyperLiquidProvider_cacheDexMetadataFromRead, _HyperLiquidProvider_backfillAssetMapForDex, _HyperLiquidProvider_getAssetIdWithRepair, _HyperLiquidProvider_getCachedSpotMeta, _HyperLiquidProvider_getCachedPerpDexs, _HyperLiquidProvider_calculateHip3FeeMultiplier, _HyperLiquidProvider_getCacheKey, _HyperLiquidProvider_getApprovedBuilderKey, _HyperLiquidProvider_fetchMarketsForDex, _HyperLiquidProvider_getUsdcTokenId, _HyperLiquidProvider_isUsdcCollateralDex, _HyperLiquidProvider_buildAssetMapping, _HyperLiquidProvider_queryUserDataAcrossDexs, _HyperLiquidProvider_mapError, _HyperLiquidProvider_getErrorContext, _HyperLiquidProvider_isMappedAccountModeExchangeError, _HyperLiquidProvider_getTradingErrorContext, _HyperLiquidProvider_checkBuilderFeeApproval, _HyperLiquidProvider_ensureBuilderFeeApproval, _HyperLiquidProvider_checkBuilderFeeStatus, _HyperLiquidProvider_getBalanceForDex, _HyperLiquidProvider_findSourceDexWithBalance, _HyperLiquidProvider_autoTransferForHip3Order, _HyperLiquidProvider_autoTransferBackAfterClose, _HyperLiquidProvider_calculateHip3RequiredMargin, _HyperLiquidProvider_handleHip3PostOrderRebalance, _HyperLiquidProvider_handleHip3OrderRollback, _HyperLiquidProvider_validateOrderBeforePlacement, _HyperLiquidProvider_getAssetInfo, _HyperLiquidProvider_prepareAssetForTrading, _HyperLiquidProvider_handleHip3PreOrder, _HyperLiquidProvider_submitOrderWithRollback, _HyperLiquidProvider_handleOrderError, _HyperLiquidProvider_placeStrategyOrder, _HyperLiquidProvider_submitPreparedStrategy, _HyperLiquidProvider_prepareStrategyPlacement, _HyperLiquidProvider_getMinimumOrderSize, _HyperLiquidProvider_buildScaleLadder, _HyperLiquidProvider_placeTwapOrder, _HyperLiquidProvider_placeScaleOrder, _HyperLiquidProvider_startChaseSession, _HyperLiquidProvider_getChaseQuotePrice, _HyperLiquidProvider_restChaseOrder, _HyperLiquidProvider_scheduleChaseTick, _HyperLiquidProvider_pauseChaseTicksForPlacement, _HyperLiquidProvider_resumeChaseTicksAfterPlacement, _HyperLiquidProvider_recoverFailedChaseTick, _HyperLiquidProvider_runChaseTick, _HyperLiquidProvider_resolveOwnRestingSizes, _HyperLiquidProvider_readOrderRemainder, _HyperLiquidProvider_cancelChaseChild, _HyperLiquidProvider_retractOrphanedChaseOrder, _HyperLiquidProvider_stopChaseSession, _HyperLiquidProvider_rebalanceChaseSession, _HyperLiquidProvider_adaptTwapOrder, _HyperLiquidProvider_rebalanceTrackedTwapOrder, _HyperLiquidProvider_rebalanceTerminalHip3Twaps, _HyperLiquidProvider_cancelStrategyOrder, _HyperLiquidProvider_cancelTwapOrder, _HyperLiquidProvider_cancelScaleOrder, _HyperLiquidProvider_cancelChaseOrder, _HyperLiquidProvider_performChaseTermination, _HyperLiquidProvider_cancelOrderRequests, _HyperLiquidProvider_cancelOrderRequestBatch, _HyperLiquidProvider_readOrderPlacementOutcome, _HyperLiquidProvider_readOrderIdFromStatus, _HyperLiquidProvider_getDiscountedBuilderFee, _HyperLiquidProvider_getBuilderOrderContext, _HyperLiquidProvider_fetchOpenOrders, _HyperLiquidProvider_resolveReplacementOrderId, _HyperLiquidProvider_getStandaloneValidatedDexs, _HyperLiquidProvider_queryDexPositions, _HyperLiquidProvider_restoreScaleOrderGroups, _HyperLiquidProvider_getAllMids, _HyperLiquidProvider_fetchSingleDexFresh, _HyperLiquidProvider_excludeNonUsdcCollateralResults, _HyperLiquidProvider_mergeDexResultsInto, _HyperLiquidProvider_cacheFreshMarketDataSnapshot, _HyperLiquidProvider_getStaleMarketDataSnapshot, _HyperLiquidProvider_isFeeCacheValid, _HyperLiquidProvider_performDisconnect, _HyperLiquidProvider_getBuilderAddress, _HyperLiquidProvider_getSubscriptionBuilderAddress, _HyperLiquidProvider_getReferralCode, _HyperLiquidProvider_ensureReferralSet, _HyperLiquidProvider_isReferralCodeReady, _HyperLiquidProvider_checkReferralSet, _HyperLiquidProvider_setReferralCode;
13
+ import { hasProperty, isHexString } from "@metamask/utils";
14
+ import { BigNumber } from "bignumber.js";
14
15
  import { v4 as uuidv4 } from "uuid";
15
16
  import { PERPS_EVENT_PROPERTY, PERPS_EVENT_VALUE } from "../constants/eventNames.mjs";
16
- import { BASIS_POINTS_DIVISOR, BUILDER_FEE_CONFIG, canonicalizeHyperLiquidDexes, FEE_RATES, getBridgeInfo, getChainId, HIP3_ASSET_MARKET_TYPES, HIP3_FEE_CONFIG, HIP3_MARGIN_CONFIG, HYPERLIQUID_ASSET_NAMES, HYPERLIQUID_CONFIG, HYPERLIQUID_WITHDRAWAL_MINUTES, REFERRAL_CONFIG, TRADING_DEFAULTS, USDC_DECIMALS, USDC_SYMBOL } from "../constants/hyperLiquidConfig.mjs";
17
- import { CHASE_ORDER_CONFIG, CHASE_ORDER_STATUS, HYPERLIQUID_TWAP_LIMITS, ORDER_SLIPPAGE_CONFIG, PERFORMANCE_CONFIG, PERPS_CONSTANTS, TP_SL_CONFIG, WITHDRAWAL_CONSTANTS } from "../constants/perpsConfig.mjs";
17
+ import { BASIS_POINTS_DIVISOR, BUILDER_FEE_CONFIG, canonicalizeHyperLiquidDexes, FEE_RATES, HYPERLIQUID_ORDER_CAPABILITIES, getBridgeInfo, getChainId, HIP3_ASSET_MARKET_TYPES, HIP3_FEE_CONFIG, HIP3_MARGIN_CONFIG, HYPERLIQUID_ASSET_NAMES, HYPERLIQUID_CONFIG, HYPERLIQUID_WITHDRAWAL_MINUTES, REFERRAL_CONFIG, TRADING_DEFAULTS, USDC_DECIMALS, USDC_SYMBOL } from "../constants/hyperLiquidConfig.mjs";
18
+ import { CHASE_ORDER_CONFIG, CHASE_ORDER_STATUS, HYPERLIQUID_TWAP_LIMITS, ORDER_SLIPPAGE_CONFIG, PERFORMANCE_CONFIG, PERPS_CONSTANTS, PROVIDER_CONFIG, TP_SL_CONFIG, WITHDRAWAL_CONSTANTS } from "../constants/perpsConfig.mjs";
18
19
  import { PERPS_TRANSACTIONS_HISTORY_CONSTANTS } from "../constants/transactionsHistoryConfig.mjs";
19
20
  import { PERPS_ERROR_CODES } from "../perpsErrorCodes.mjs";
20
21
  import { DexDiscoveryCacheManager } from "../services/DexDiscoveryCacheManager.mjs";
@@ -25,16 +26,18 @@ import { TradingReadinessCache, PerpsSigningCache } from "../services/TradingRea
25
26
  import { HL_ABSTRACTION_WIRE, HL_UNIFIED_ACCOUNT_MODE, hyperLiquidModeFoldsSpot } from "../types/hyperliquid-types.mjs";
26
27
  import { PerpsAnalyticsEvent } from "../types/index.mjs";
27
28
  import { addSpotBalanceToAccountState, aggregateAccountStates } from "../utils/accountUtils.mjs";
29
+ import { isValidCapabilitySymbol } from "../utils/capabilitySymbols.mjs";
28
30
  import { ensureError, isHyperLiquidMultiSigRequiredError, isHyperLiquidUserNotFoundError, isKeyringLockedError } from "../utils/errorUtils.mjs";
29
31
  import { shouldDeferUnifiedAccountSetup } from "../utils/hyperLiquidAbstraction.mjs";
30
- import { adaptAccountStateFromSDK, adaptHyperLiquidLedgerUpdateToUserHistoryItem, adaptMarketFromSDK, adaptOrderFromSDK, adaptPositionFromSDK, adaptPositionTriggerOrderFromSDK, adaptTpslLinkageToGrouping, buildAssetMapping, formatHyperLiquidPrice, formatHyperLiquidSize, parseAssetName } from "../utils/hyperLiquidAdapter.mjs";
32
+ import { adaptAccountStateFromSDK, adaptHyperLiquidLedgerUpdateToUserHistoryItem, adaptMarketFromSDK, adaptOrderFromSDK, adaptPositionFromSDK, adaptPositionTriggerOrderFromSDK, adaptTpslLinkageToGrouping, buildAssetMapping, formatHyperLiquidPrice, formatHyperLiquidSize, HYPERLIQUID_SCALE_CLOID_MARKER, parseAssetName } from "../utils/hyperLiquidAdapter.mjs";
31
33
  import { createErrorResult, getMaxOrderValue, getSupportedPaths, validateAssetSupport, validateBalance, validateCoinExists, validateDepositParams, validateOrderParams, validateWithdrawalParams } from "../utils/hyperLiquidValidation.mjs";
32
34
  import { generatePerpsId } from "../utils/idUtils.mjs";
33
35
  import { transformMarketData } from "../utils/marketDataTransform.mjs";
34
36
  import { compileMarketPattern, shouldIncludeMarket } from "../utils/marketUtils.mjs";
35
37
  import { buildOrdersArray, calculateFinalPositionSize, calculateOrderPriceAndSize, computeChaseQuotePrice, computeScalePriceLadder, floorToSizeDecimals, formatPartialTpslSize, getPriceTick, splitScaleSizes, validateOrderPrecision } from "../utils/orderCalculations.mjs";
36
- import { getTriggerExecution, isLimitExecutionOrderType, isStrategyOrderType, isTriggerOrderType, resolvePositionTriggerSummaryPrice, toSDKTimeInForce } from "../utils/orderTypes.mjs";
38
+ import { getTriggerDirection, getTriggerExecution, isLimitExecutionOrderType, isStrategyOrderType, isTriggerOrderType, resolvePositionTriggerSummaryPrice, toSDKTimeInForce } from "../utils/orderTypes.mjs";
37
39
  import { createStandaloneInfoClient, queryStandaloneClearinghouseStates, queryStandaloneOpenOrders } from "../utils/standaloneInfoClient.mjs";
40
+ import { parseBoundedNonNegativeDecimal } from "../utils/stringParseUtils.mjs";
38
41
  // getStreamManagerInstance removed: use this.#deps.streamManager instead
39
42
  /**
40
43
  * Type guard to check if a status is an object (not a string literal like "waitingForFill")
@@ -59,15 +62,54 @@ const ALREADY_GONE_CANCEL_MARKERS = [
59
62
  'already canceled',
60
63
  'already cancelled',
61
64
  'order not found',
65
+ 'twap not found',
62
66
  ];
63
67
  const RETRYABLE_CHASE_PLACEMENT_MARKERS = [
64
68
  'post only order would have immediately matched',
65
69
  'price too far from oracle',
66
70
  ];
71
+ const CHASE_ORDER_STATUS_RETRY_COUNT = 2;
72
+ const CHASE_ORDER_STATUS_RETRY_DELAY_MS = 100;
73
+ const CHASE_ORDER_STATUS_UNAVAILABLE = 'status_unavailable';
74
+ class ChaseOrderStatusUnavailableError extends Error {
75
+ constructor() {
76
+ super('Chase order status unavailable');
77
+ this.name = 'ChaseOrderStatusUnavailableError';
78
+ }
79
+ }
67
80
  const isRetryableChasePlacementError = (error) => {
68
81
  const message = ensureError(error, 'HyperLiquidProvider.startChaseSession').message.toLowerCase();
69
82
  return RETRYABLE_CHASE_PLACEMENT_MARKERS.some((marker) => message.includes(marker));
70
83
  };
84
+ /**
85
+ * What happened to one order a cancel was asked to remove.
86
+ *
87
+ * `Cancelled` and `Gone` both mean nothing of it is resting, which is all a
88
+ * caller asked for; only `Refused` leaves an order behind.
89
+ */
90
+ var CancelChildOutcome;
91
+ (function (CancelChildOutcome) {
92
+ CancelChildOutcome["Cancelled"] = "cancelled";
93
+ CancelChildOutcome["Gone"] = "gone";
94
+ CancelChildOutcome["Refused"] = "refused";
95
+ })(CancelChildOutcome || (CancelChildOutcome = {}));
96
+ var HyperLiquidTwapLifecycleStatus;
97
+ (function (HyperLiquidTwapLifecycleStatus) {
98
+ HyperLiquidTwapLifecycleStatus["Activated"] = "activated";
99
+ HyperLiquidTwapLifecycleStatus["Failed"] = "error";
100
+ HyperLiquidTwapLifecycleStatus["Finished"] = "finished";
101
+ HyperLiquidTwapLifecycleStatus["Stopped"] = "stopped";
102
+ HyperLiquidTwapLifecycleStatus["Terminated"] = "terminated";
103
+ HyperLiquidTwapLifecycleStatus["WaitingForTrigger"] = "waitingForTrigger";
104
+ })(HyperLiquidTwapLifecycleStatus || (HyperLiquidTwapLifecycleStatus = {}));
105
+ var PerpsTwapLifecycleStatus;
106
+ (function (PerpsTwapLifecycleStatus) {
107
+ PerpsTwapLifecycleStatus["Active"] = "active";
108
+ PerpsTwapLifecycleStatus["Canceled"] = "canceled";
109
+ PerpsTwapLifecycleStatus["Completed"] = "completed";
110
+ PerpsTwapLifecycleStatus["CompletedUnderfilled"] = "completed_underfilled";
111
+ PerpsTwapLifecycleStatus["Failed"] = "failed";
112
+ })(PerpsTwapLifecycleStatus || (PerpsTwapLifecycleStatus = {}));
71
113
  /**
72
114
  * Classify one entry of a cancel response.
73
115
  *
@@ -76,14 +118,14 @@ const isRetryableChasePlacementError = (error) => {
76
118
  */
77
119
  const classifyCancelStatus = (status) => {
78
120
  if (status === 'success') {
79
- return 'cancelled';
121
+ return CancelChildOutcome.Cancelled;
80
122
  }
81
123
  const message = (isStatusObject(status) && typeof status.error === 'string'
82
124
  ? status.error
83
125
  : '').toLowerCase();
84
126
  return ALREADY_GONE_CANCEL_MARKERS.some((marker) => message.includes(marker))
85
- ? 'gone'
86
- : 'refused';
127
+ ? CancelChildOutcome.Gone
128
+ : CancelChildOutcome.Refused;
87
129
  };
88
130
  /**
89
131
  * Read the best price on one side of the book, discounting the chase's own
@@ -185,6 +227,29 @@ const validateStrategyNotional = (params) => {
185
227
  }
186
228
  return { isValid: true };
187
229
  };
230
+ /**
231
+ * Create one recoverable identity for a Scale ladder and one venue CLOID per
232
+ * rung. HyperLiquid requires every CLOID to be unique, so the last byte holds
233
+ * the rung index while the preceding 15 bytes identify the shared group.
234
+ *
235
+ * @param count - Number of ladder rungs.
236
+ * @returns The public group handle and venue client order IDs.
237
+ */
238
+ const createScaleOrderIdentity = (count) => {
239
+ const groupKey = `${HYPERLIQUID_SCALE_CLOID_MARKER}${uuidv4()
240
+ .replace(/-/gu, '')
241
+ .slice(0, 22)}`;
242
+ const clientOrderIds = Array.from({ length: count }, (_, index) => {
243
+ const clientOrderId = `0x${groupKey}${index
244
+ .toString(16)
245
+ .padStart(2, '0')}`;
246
+ if (!isHexString(clientOrderId)) {
247
+ throw new Error('Failed to create Scale client order ID');
248
+ }
249
+ return clientOrderId;
250
+ });
251
+ return { groupId: `scale:${groupKey}`, clientOrderIds };
252
+ };
188
253
  /**
189
254
  * Collect the order IDs of every TP/SL child carried by a parent order.
190
255
  *
@@ -296,6 +361,76 @@ function collectPositionTriggerOrders(params) {
296
361
  ...(stopLossSummaryPrice && { stopLossPrice: stopLossSummaryPrice }),
297
362
  };
298
363
  }
364
+ const MAX_API_FEE_RATE = 1;
365
+ const MAX_API_FEE_DISCOUNT = 1;
366
+ /**
367
+ * Fee applicability by canonical order type.
368
+ *
369
+ * The configuration type makes this map exhaustive, so a new order type cannot
370
+ * inherit a builder-fee policy accidentally. HyperLiquid's dedicated TWAP
371
+ * action has no builder field. Every other standalone placement uses an order
372
+ * action that can carry one builder context. This policy is deliberately not a
373
+ * constructor or order input. A future backend policy must be resolved inside
374
+ * the provider from its trusted source rather than accepted from a client.
375
+ */
376
+ const HYPERLIQUID_ORDER_FEE_CONFIG = {
377
+ market: { chargesMetamaskBuilderFee: true },
378
+ limit: { chargesMetamaskBuilderFee: true },
379
+ stop_market: { chargesMetamaskBuilderFee: true },
380
+ stop_limit: { chargesMetamaskBuilderFee: true },
381
+ take_profit_market: { chargesMetamaskBuilderFee: true },
382
+ take_profit_limit: { chargesMetamaskBuilderFee: true },
383
+ twap: { chargesMetamaskBuilderFee: false },
384
+ scale: { chargesMetamaskBuilderFee: true },
385
+ chase: { chargesMetamaskBuilderFee: true },
386
+ };
387
+ const MILLISECONDS_PER_MINUTE = 60000;
388
+ const MILLISECONDS_PER_SECOND = 1000;
389
+ /**
390
+ * Normalize the TWAP history timestamp documented in seconds while tolerating
391
+ * an already-millisecond value from a future SDK response.
392
+ *
393
+ * @param timestamp - Venue timestamp in seconds or milliseconds.
394
+ * @returns Timestamp in milliseconds.
395
+ */
396
+ const normalizeTwapHistoryTimestamp = (timestamp) => timestamp < 1000000000000
397
+ ? timestamp * MILLISECONDS_PER_SECOND
398
+ : timestamp;
399
+ const getTwapOrderScopeKey = (params) => `${params.network}:${params.userAddress.toLowerCase()}:${params.orderId}`;
400
+ const resolveTwapOrderStatus = (historyEntry, executedSize, totalSize) => {
401
+ switch (historyEntry.status.status) {
402
+ case HyperLiquidTwapLifecycleStatus.Activated:
403
+ case HyperLiquidTwapLifecycleStatus.WaitingForTrigger:
404
+ return PerpsTwapLifecycleStatus.Active;
405
+ case HyperLiquidTwapLifecycleStatus.Finished:
406
+ return executedSize.isLessThan(totalSize)
407
+ ? PerpsTwapLifecycleStatus.CompletedUnderfilled
408
+ : PerpsTwapLifecycleStatus.Completed;
409
+ case HyperLiquidTwapLifecycleStatus.Terminated:
410
+ case HyperLiquidTwapLifecycleStatus.Stopped:
411
+ return PerpsTwapLifecycleStatus.Canceled;
412
+ case HyperLiquidTwapLifecycleStatus.Failed:
413
+ return PerpsTwapLifecycleStatus.Failed;
414
+ default:
415
+ // A new venue status is not proof that a schedule stopped. Keep it
416
+ // non-terminal so collateral cannot be reclaimed from a live TWAP.
417
+ return PerpsTwapLifecycleStatus.Active;
418
+ }
419
+ };
420
+ const adaptTwapOrderFill = (entry) => ({
421
+ fillId: entry.fill.tid.toString(),
422
+ orderId: entry.fill.oid.toString(),
423
+ side: entry.fill.side === 'B' ? 'buy' : 'sell',
424
+ price: entry.fill.px,
425
+ size: entry.fill.sz,
426
+ fee: entry.fill.fee,
427
+ feeToken: entry.fill.feeToken,
428
+ ...(entry.fill.builderFee === undefined
429
+ ? {}
430
+ : { builderFee: entry.fill.builderFee }),
431
+ timestamp: entry.fill.time,
432
+ transactionHash: entry.fill.hash,
433
+ });
299
434
  /**
300
435
  * HyperLiquid provider implementation
301
436
  *
@@ -310,7 +445,7 @@ function collectPositionTriggerOrders(params) {
310
445
  export class HyperLiquidProvider {
311
446
  constructor(options) {
312
447
  _HyperLiquidProvider_instances.add(this);
313
- this.protocolId = 'hyperliquid';
448
+ this.protocolId = PROVIDER_CONFIG.DefaultProvider;
314
449
  // Platform dependencies for logging and debugging
315
450
  _HyperLiquidProvider_deps.set(this, void 0);
316
451
  // Service instances
@@ -326,6 +461,16 @@ export class HyperLiquidProvider {
326
461
  // Cache for raw meta responses (shared across methods to avoid redundant API calls)
327
462
  // Filtering is applied on-demand (cheap array operations) - no need for separate processed cache
328
463
  _HyperLiquidProvider_cachedMetaByDex.set(this, new Map());
464
+ // Fresh filtered markets used only for capability discovery. Each routed
465
+ // DEX owns its freshness window and coalesced refresh.
466
+ _HyperLiquidProvider_orderCapabilitiesMarketsByDex.set(this, new Map());
467
+ _HyperLiquidProvider_orderCapabilitiesRefreshByDex.set(this, new Map());
468
+ // Sticky for this instance. PerpsController discards disconnected providers
469
+ // during initialization; direct callers must call initialize() before reuse.
470
+ _HyperLiquidProvider_isDisconnected.set(this, false);
471
+ _HyperLiquidProvider_disconnectOperationsInFlight.set(this, 0);
472
+ _HyperLiquidProvider_disconnectOperationPromise.set(this, null);
473
+ _HyperLiquidProvider_lifecycleGeneration.set(this, 0);
329
474
  // Last known-good market list for stale fallback when every enabled DEX fails in one fetch window.
330
475
  _HyperLiquidProvider_cachedMarketDataWithPrices.set(this, null);
331
476
  // Session cache for spot metadata (contains token info for HIP-3 collateral checks)
@@ -358,12 +503,9 @@ export class HyperLiquidProvider {
358
503
  _HyperLiquidProvider_hip3Enabled.set(this, void 0);
359
504
  _HyperLiquidProvider_allowlistMarkets.set(this, void 0);
360
505
  _HyperLiquidProvider_blocklistMarkets.set(this, void 0);
361
- // Emergency kill-switch for the Unified Account migration flow. Defaults
362
- // to true and is the expected production state after HL's DEX Abstraction
363
- // deprecation. Kept as a constructor option (not removed) so we can
364
- // disable the migration via a hot-fix release if a regression surfaces
365
- // in the wild — flipping this to false reverts to the legacy programmatic
366
- // HIP-3 transfer path that already lives in the codebase.
506
+ // Legacy rollback switch. MetaMask production supports unified account and
507
+ // portfolio margin. The false branch and its manual HIP-3 transfers remain
508
+ // temporarily for rollback compatibility and will be removed separately.
367
509
  _HyperLiquidProvider_useUnifiedAccount.set(this, void 0);
368
510
  // True once DEX discovery has succeeded with real data (not a fallback).
369
511
  // When false, #ensureReadyPromise is reset after each init so the next
@@ -390,6 +532,10 @@ export class HyperLiquidProvider {
390
532
  // one cancel can reach every rung. Cleared on disconnect; the rung IDs are
391
533
  // also returned to the caller as `OrderResult.childOrderIds`.
392
534
  _HyperLiquidProvider_scaleOrderGroups.set(this, new Map());
535
+ // Prevent a stale open-order cache from recreating a group that this
536
+ // provider lifecycle already canceled successfully.
537
+ _HyperLiquidProvider_cancelledScaleOrderGroups.set(this, new Set());
538
+ _HyperLiquidProvider_trackedTwapOrders.set(this, void 0);
393
539
  // Chase sessions running on this provider instance, keyed by session handle.
394
540
  // Each owns a pending timer, so disconnect has to stop them.
395
541
  _HyperLiquidProvider_chaseSessions.set(this, new Map());
@@ -412,11 +558,9 @@ export class HyperLiquidProvider {
412
558
  // transport, so simultaneous cancel/replace ticks would create avoidable
413
559
  // signed traffic and can starve a foreground placement.
414
560
  _HyperLiquidProvider_chaseTickQueue.set(this, Promise.resolve());
415
- // Bumped by every teardown that clears the chase registry. A placement
416
- // captures it before its round trips and refuses to register afterwards if it
417
- // has moved: a session registered after a disconnect would schedule a
418
- // background timer against a provider that has already been torn down.
419
- _HyperLiquidProvider_chaseGeneration.set(this, 0);
561
+ // Every strategy captures this before its round trips. A changed generation
562
+ // prevents a late response from registering handles after disconnect.
563
+ _HyperLiquidProvider_strategyGeneration.set(this, 0);
420
564
  // Reference-counted because suspension and disconnect may overlap. One
421
565
  // lifecycle owner finishing must not reopen placement while another still
422
566
  // drains or tears down the provider.
@@ -431,27 +575,35 @@ export class HyperLiquidProvider {
431
575
  _HyperLiquidProvider_subscriptionBuilderAddressTestnet.set(this, void 0);
432
576
  _HyperLiquidProvider_subscriptionBuilderAddressMainnet.set(this, void 0);
433
577
  _HyperLiquidProvider_priceDeviationLimit.set(this, void 0);
578
+ _HyperLiquidProvider_onChaseOrderMaxDistanceReached.set(this, void 0);
434
579
  /**
435
580
  * Ensure provider is ready for TRADING operations (signing required)
436
581
  *
437
- * This method performs additional setup that requires user signatures:
582
+ * This method performs shared setup that may require user signatures:
438
583
  * - DEX abstraction enablement (for HIP-3 auto-transfers)
439
- * - Builder fee approval (required for orders)
440
584
  * - Referral code setup (attribution)
585
+ * - Builder fee approval when the whole action carries a builder context
441
586
  *
442
587
  * These operations are DEFERRED from ensureReady() to avoid hardware wallet prompt spam
443
588
  * when users are just viewing the Perps section (critical for hardware wallets).
444
589
  *
445
- * Call this method before any trading operation (placeOrder, cancelOrder, etc.)
590
+ * General trading readiness is independent of builder approval. Native TWAP
591
+ * and cancellations call this with `requiresBuilderFee: false`.
446
592
  */
447
593
  _HyperLiquidProvider_tradingSetupPromise.set(this, null);
448
594
  _HyperLiquidProvider_tradingSetupComplete.set(this, false);
595
+ _HyperLiquidProvider_builderFeeSetupPromises.set(this, new Map());
449
596
  __classPrivateFieldSet(this, _HyperLiquidProvider_deps, options.platformDependencies, "f");
450
597
  __classPrivateFieldSet(this, _HyperLiquidProvider_messenger, options.messenger, "f");
598
+ const trackedTwapOrders = __classPrivateFieldGet(_a, _a, "f", _HyperLiquidProvider_trackedTwapOrdersByMessenger).get(__classPrivateFieldGet(this, _HyperLiquidProvider_messenger, "f")) ??
599
+ new Map();
600
+ __classPrivateFieldGet(_a, _a, "f", _HyperLiquidProvider_trackedTwapOrdersByMessenger).set(__classPrivateFieldGet(this, _HyperLiquidProvider_messenger, "f"), trackedTwapOrders);
601
+ __classPrivateFieldSet(this, _HyperLiquidProvider_trackedTwapOrders, trackedTwapOrders, "f");
451
602
  __classPrivateFieldSet(this, _HyperLiquidProvider_builderAddressTestnet, options.builderAddressTestnet, "f");
452
603
  __classPrivateFieldSet(this, _HyperLiquidProvider_builderAddressMainnet, options.builderAddressMainnet, "f");
453
604
  __classPrivateFieldSet(this, _HyperLiquidProvider_subscriptionBuilderAddressTestnet, options.subscriptionBuilderAddressTestnet, "f");
454
605
  __classPrivateFieldSet(this, _HyperLiquidProvider_subscriptionBuilderAddressMainnet, options.subscriptionBuilderAddressMainnet, "f");
606
+ __classPrivateFieldSet(this, _HyperLiquidProvider_onChaseOrderMaxDistanceReached, options.onChaseOrderMaxDistanceReached, "f");
455
607
  __classPrivateFieldSet(this, _HyperLiquidProvider_priceDeviationLimit, options.priceDeviationLimit ??
456
608
  HYPERLIQUID_CONFIG.OraclePriceDeviationLimit, "f");
457
609
  const isTestnet = options.isTestnet ?? false;
@@ -501,6 +653,56 @@ export class HyperLiquidProvider {
501
653
  isTestnet,
502
654
  });
503
655
  }
656
+ /**
657
+ * Return provider-owned strategy support for a routed market.
658
+ * HyperLiquid metadata has no per-market strategy flags. This provider
659
+ * advertises its implemented strategies uniformly after confirming the
660
+ * routed market exists.
661
+ *
662
+ * @param params - Required market route context.
663
+ * @returns Supported strategy order types.
664
+ */
665
+ async getOrderCapabilities(params) {
666
+ if (!isValidCapabilitySymbol(params.symbol, { allowProviderRoute: true })) {
667
+ return {
668
+ status: 'unavailable',
669
+ providerId: this.protocolId,
670
+ reason: 'invalid_symbol',
671
+ };
672
+ }
673
+ const { dex } = parseAssetName(params.symbol);
674
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_isDisconnected, "f")) {
675
+ return {
676
+ status: 'unavailable',
677
+ providerId: this.protocolId,
678
+ reason: 'provider_unavailable',
679
+ };
680
+ }
681
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
682
+ try {
683
+ const marketExists = (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getFreshOrderCapabilityMarkets).call(this, dex)).some((market) => market.name === params.symbol);
684
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Order capability read');
685
+ return marketExists
686
+ ? HYPERLIQUID_ORDER_CAPABILITIES
687
+ : {
688
+ status: 'unavailable',
689
+ providerId: this.protocolId,
690
+ reason: 'market_not_found',
691
+ };
692
+ }
693
+ catch (error) {
694
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Order capabilities unavailable', {
695
+ symbol: params.symbol,
696
+ error: ensureError(error, 'HyperLiquidProvider.getOrderCapabilities')
697
+ .message,
698
+ });
699
+ return {
700
+ status: 'unavailable',
701
+ providerId: this.protocolId,
702
+ reason: 'provider_unavailable',
703
+ };
704
+ }
705
+ }
504
706
  /**
505
707
  * Get fills using WebSocket cache first, falling back to REST API
506
708
  * OPTIMIZATION: Uses cached fills when available (0 API weight), only calls REST on cache miss
@@ -759,7 +961,10 @@ export class HyperLiquidProvider {
759
961
  // Ensure provider is ready for trading (includes signing operations).
760
962
  // Kept after validation so invalid orders never trigger signature prompts
761
963
  // (builder-fee approval, DEX abstraction enablement, etc.).
762
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
964
+ const { chargesMetamaskBuilderFee } = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, params);
965
+ const builderFeeSetupContext = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, {
966
+ requiresBuilderFee: chargesMetamaskBuilderFee,
967
+ });
763
968
  // Debug: Log asset map state before order placement
764
969
  const allMapKeys = Array.from(__classPrivateFieldGet(this, _HyperLiquidProvider_symbolToAssetId, "f").keys());
765
970
  const hip3Keys = allMapKeys.filter((key) => key.includes(':'));
@@ -864,6 +1069,8 @@ export class HyperLiquidProvider {
864
1069
  transferInfo,
865
1070
  symbol: params.symbol,
866
1071
  assetId,
1072
+ chargesMetamaskBuilderFee,
1073
+ builderFeeSetupContext,
867
1074
  });
868
1075
  }
869
1076
  catch (error) {
@@ -930,6 +1137,54 @@ export class HyperLiquidProvider {
930
1137
  });
931
1138
  }
932
1139
  }
1140
+ /**
1141
+ * Read native TWAP schedules and their slice fills from the venue.
1142
+ *
1143
+ * @returns Current and terminal TWAP lifecycle records, newest first.
1144
+ */
1145
+ async getTwapOrders() {
1146
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureClientsInitialized).call(this);
1147
+ __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").ensureInitialized();
1148
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
1149
+ const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient();
1150
+ const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
1151
+ const [history, sliceFills] = await Promise.all([
1152
+ infoClient.twapHistory({ user: userAddress }),
1153
+ infoClient.userTwapSliceFills({ user: userAddress }),
1154
+ ]);
1155
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'TWAP lifecycle read');
1156
+ const fillsByTwapId = new Map();
1157
+ for (const sliceFill of sliceFills) {
1158
+ const existing = fillsByTwapId.get(sliceFill.twapId) ?? [];
1159
+ existing.push(sliceFill);
1160
+ fillsByTwapId.set(sliceFill.twapId, existing);
1161
+ }
1162
+ const now = Date.now();
1163
+ const ordersById = new Map();
1164
+ for (const historyEntry of history) {
1165
+ const order = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_adaptTwapOrder).call(this, {
1166
+ historyEntry,
1167
+ sliceFills: historyEntry.twapId === undefined
1168
+ ? []
1169
+ : (fillsByTwapId.get(historyEntry.twapId) ?? []),
1170
+ now,
1171
+ });
1172
+ if (!order) {
1173
+ continue;
1174
+ }
1175
+ const existing = ordersById.get(order.orderId);
1176
+ if (!existing || order.lastUpdated >= existing.lastUpdated) {
1177
+ ordersById.set(order.orderId, order);
1178
+ }
1179
+ }
1180
+ const orders = [...ordersById.values()].sort((left, right) => right.startedAt - left.startedAt);
1181
+ const network = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode() ? 'testnet' : 'mainnet';
1182
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceTerminalHip3Twaps).call(this, orders, {
1183
+ network,
1184
+ userAddress,
1185
+ });
1186
+ return orders;
1187
+ }
933
1188
  /**
934
1189
  * Return stable client-facing snapshots for every Chase session retained by
935
1190
  * this provider instance. A read may refresh the current child order's
@@ -947,12 +1202,26 @@ export class HyperLiquidProvider {
947
1202
  session.lastSnapshotSizeRefreshAt = now;
948
1203
  const snapshotOrderId = session.orderId;
949
1204
  try {
950
- const liveRemainder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, snapshotOrderId);
951
- if (liveRemainder !== null &&
952
- session.orderId === snapshotOrderId &&
1205
+ const liveRemainder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, snapshotOrderId, CHASE_ORDER_STATUS_RETRY_COUNT);
1206
+ if (session.orderId === snapshotOrderId &&
953
1207
  session.pendingReplacement === null) {
954
- Object.assign(session, { size: liveRemainder });
955
- snapshotRemainingSize = liveRemainder;
1208
+ if (liveRemainder.terminalStatus === null) {
1209
+ if (liveRemainder.remainingSize !== null) {
1210
+ session.size = liveRemainder.remainingSize;
1211
+ snapshotRemainingSize = liveRemainder.remainingSize;
1212
+ }
1213
+ }
1214
+ else {
1215
+ session.orderId = null;
1216
+ session.size = liveRemainder.remainingSize ?? '0';
1217
+ session.active = false;
1218
+ if (session.timer) {
1219
+ clearTimeout(session.timer);
1220
+ session.timer = null;
1221
+ }
1222
+ session.status = liveRemainder.terminalStatus;
1223
+ snapshotRemainingSize = session.size;
1224
+ }
956
1225
  }
957
1226
  }
958
1227
  catch (error) {
@@ -963,6 +1232,12 @@ export class HyperLiquidProvider {
963
1232
  });
964
1233
  }
965
1234
  }
1235
+ if (!session.active && session.hip3Transfer) {
1236
+ const rebalanced = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
1237
+ if (rebalanced && session.removeAfterRebalance) {
1238
+ __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").delete(handle);
1239
+ }
1240
+ }
966
1241
  const arrival = Number.parseFloat(session.arrivalPrice);
967
1242
  const resting = Number.parseFloat(session.restingPrice);
968
1243
  const adverseDistance = session.isBuy
@@ -1000,9 +1275,6 @@ export class HyperLiquidProvider {
1000
1275
  */
1001
1276
  async suspendChaseOrders() {
1002
1277
  __classPrivateFieldSet(this, _HyperLiquidProvider_chasePlacementBlockers, __classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") + 1, "f");
1003
- // Abort a placement that passed the blocker check but has not completed
1004
- // its final generation check; the waiter below then drains its cleanup.
1005
- __classPrivateFieldSet(this, _HyperLiquidProvider_chaseGeneration, __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f") + 1, "f");
1006
1278
  try {
1007
1279
  await Promise.all([...__classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementWaiters, "f")]);
1008
1280
  // Clear timers without deactivating sessions, then let any tick already
@@ -1185,10 +1457,10 @@ export class HyperLiquidProvider {
1185
1457
  ? params.newOrder.clientOrderId
1186
1458
  : undefined,
1187
1459
  };
1188
- // Every refusal is behind us, so the setup that may prompt for signatures
1189
- // and write builder-fee/referral approvals can run now — a rejected edit
1190
- // costs the caller nothing, matching placeOrder and updatePositionTPSL.
1191
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
1460
+ // Every refusal is behind us, so shared trading readiness can run now.
1461
+ // HyperLiquid's modify action has no builder field and must not request a
1462
+ // builder-fee approval.
1463
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
1192
1464
  // Submit modification via SDK
1193
1465
  const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
1194
1466
  const result = await exchangeClient.modify({
@@ -1253,24 +1525,25 @@ export class HyperLiquidProvider {
1253
1525
  if (owningChase) {
1254
1526
  __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_stopChaseSession).call(this, owningChase[0]);
1255
1527
  await __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f");
1256
- return await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelChaseOrder).call(this, {
1528
+ const result = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelChaseOrder).call(this, {
1257
1529
  ...params,
1258
1530
  orderId: owningChase[0],
1259
1531
  orderType: 'chase',
1260
1532
  });
1533
+ return { ...result, orderId: params.orderId };
1261
1534
  }
1262
1535
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Canceling order:', params);
1263
1536
  // Hydrate the asset map before coin validation so a cold start (e.g.
1264
1537
  // service-worker restart with an empty prefetch map) can self-heal
1265
1538
  // without signature prompts on invalid cancels. Trading setup (builder
1266
- // fee, referral, unified account) runs only after validation passes,
1267
- // matching placeOrder / editOrder.
1539
+ // referral and unified-account setup runs only after validation passes,
1540
+ // matching placeOrder / editOrder. Cancel has no builder field.
1268
1541
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReady).call(this);
1269
1542
  const coinValidation = validateCoinExists(params.symbol, __classPrivateFieldGet(this, _HyperLiquidProvider_symbolToAssetId, "f"));
1270
1543
  if (!coinValidation.isValid) {
1271
1544
  throw new Error(coinValidation.error);
1272
1545
  }
1273
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
1546
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
1274
1547
  const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
1275
1548
  const asset = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
1276
1549
  symbol: params.symbol,
@@ -1294,10 +1567,10 @@ export class HyperLiquidProvider {
1294
1567
  // HyperLiquid usually rejects a cancel without throwing: the status entry
1295
1568
  // carries the raw exchange string (e.g. "multi-sig required"). Map it the
1296
1569
  // same way as a thrown rejection so callers get a standardized code
1297
- // instead of a generic message. The SDK types every status as 'success',
1298
- // so the rejection shape needs the same cast cancelOrders already uses.
1299
- const rawError = status?.error ??
1300
- 'Order cancellation failed';
1570
+ // instead of a generic message.
1571
+ const rawError = isStatusObject(status) && typeof status.error === 'string'
1572
+ ? status.error
1573
+ : 'Order cancellation failed';
1301
1574
  return createErrorResult(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mapError).call(this, new Error(rawError)), {
1302
1575
  success: false,
1303
1576
  orderId: params.orderId,
@@ -1309,7 +1582,10 @@ export class HyperLiquidProvider {
1309
1582
  orderId: params.orderId,
1310
1583
  coin: params.symbol,
1311
1584
  }));
1312
- return createErrorResult(mappedError, { success: false });
1585
+ return createErrorResult(mappedError, {
1586
+ success: false,
1587
+ orderId: params.orderId,
1588
+ });
1313
1589
  }
1314
1590
  }
1315
1591
  /**
@@ -1320,87 +1596,126 @@ export class HyperLiquidProvider {
1320
1596
  * @returns A promise that resolves to the result.
1321
1597
  */
1322
1598
  async cancelOrders(params) {
1599
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Batch canceling orders:', {
1600
+ count: params.length,
1601
+ });
1602
+ if (params.length === 0) {
1603
+ return {
1604
+ success: false,
1605
+ successCount: 0,
1606
+ failureCount: 0,
1607
+ results: [],
1608
+ };
1609
+ }
1610
+ const results = params.map((order) => ({
1611
+ orderId: order.orderId,
1612
+ symbol: order.symbol,
1613
+ success: false,
1614
+ error: PERPS_ERROR_CODES.BATCH_CANCEL_FAILED,
1615
+ }));
1616
+ // Resolve Chase ownership before the first await. A queued reprice may
1617
+ // replace the child ID, but the stable session handle still reaches it.
1618
+ const chaseSessions = [...__classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").entries()];
1619
+ const chaseRoutes = params.flatMap((order, index) => {
1620
+ const owner = chaseSessions.find(([, session]) => session.orderId === order.orderId ||
1621
+ session.replacingOrderId === order.orderId);
1622
+ return owner ? [{ handle: owner[0], index, order }] : [];
1623
+ });
1624
+ const chaseIndexes = new Set(chaseRoutes.map(({ index }) => index));
1323
1625
  try {
1324
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Batch canceling orders:', {
1325
- count: params.length,
1326
- });
1327
- if (params.length === 0) {
1328
- return {
1329
- success: false,
1330
- successCount: 0,
1331
- failureCount: 0,
1332
- results: [],
1333
- };
1626
+ for (const handle of new Set(chaseRoutes.map((route) => route.handle))) {
1627
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_stopChaseSession).call(this, handle);
1334
1628
  }
1335
- // Ensure provider is ready for trading (includes signing operations)
1336
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
1337
- const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
1338
- // Map orders to SDK format and validate coins
1339
- const cancelRequests = await Promise.all(params.map(async (order) => {
1340
- const asset = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
1629
+ if (chaseRoutes.length > 0) {
1630
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f");
1631
+ }
1632
+ for (const { handle, index, order } of chaseRoutes) {
1633
+ const result = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelStrategyOrder).call(this, {
1634
+ orderId: handle,
1341
1635
  symbol: order.symbol,
1342
- dexName: parseAssetName(order.symbol).dex,
1636
+ orderType: 'chase',
1343
1637
  });
1344
- return {
1345
- a: asset,
1346
- o: parseInt(order.orderId, 10),
1638
+ results[index] = {
1639
+ orderId: order.orderId,
1640
+ symbol: order.symbol,
1641
+ success: result.success,
1642
+ ...(result.error === undefined ? {} : { error: result.error }),
1347
1643
  };
1348
- }));
1349
- // Single batch API call
1350
- const result = await exchangeClient.cancel({
1351
- cancels: cancelRequests,
1352
- });
1353
- // Parse response statuses (one per order)
1354
- const { statuses } = result.response.data;
1355
- const successCount = statuses.filter((status) => status === 'success').length;
1356
- const failureCount = statuses.length - successCount;
1357
- return {
1358
- success: successCount > 0,
1359
- successCount,
1360
- failureCount,
1361
- results: statuses.map((status, index) => {
1362
- // Map each per-status rejection the same way cancelOrder does, so a
1363
- // batch cancel reports standardized codes rather than raw exchange
1364
- // strings for the rejections this provider recognizes.
1365
- const statusError = status?.error;
1644
+ }
1645
+ const ordinaryOrders = params.flatMap((order, index) => chaseIndexes.has(index) ? [] : [{ index, order }]);
1646
+ if (ordinaryOrders.length > 0) {
1647
+ // Cancellation carries no builder context, so it must not prompt for a
1648
+ // fee approval that the action cannot use.
1649
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
1650
+ const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
1651
+ const cancelRequests = await Promise.all(ordinaryOrders.map(async ({ order }) => {
1652
+ const asset = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
1653
+ symbol: order.symbol,
1654
+ dexName: parseAssetName(order.symbol).dex,
1655
+ });
1366
1656
  return {
1367
- orderId: params[index].orderId,
1368
- symbol: params[index].symbol,
1369
- success: status === 'success',
1370
- error: statusError
1371
- ? __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mapError).call(this, new Error(statusError)).message
1372
- : undefined,
1657
+ a: asset,
1658
+ o: parseInt(order.orderId, 10),
1373
1659
  };
1374
- }),
1375
- };
1660
+ }));
1661
+ const result = await exchangeClient.cancel({
1662
+ cancels: cancelRequests,
1663
+ });
1664
+ const statuses = result.response?.data?.statuses ?? [];
1665
+ if (result.status === 'ok' &&
1666
+ statuses.length === ordinaryOrders.length) {
1667
+ ordinaryOrders.forEach(({ index, order }, statusIndex) => {
1668
+ const status = statuses[statusIndex];
1669
+ const success = status === 'success';
1670
+ const statusError = isStatusObject(status) && typeof status.error === 'string'
1671
+ ? status.error
1672
+ : undefined;
1673
+ results[index] = {
1674
+ orderId: order.orderId,
1675
+ symbol: order.symbol,
1676
+ success,
1677
+ ...(success
1678
+ ? {}
1679
+ : {
1680
+ error: statusError === undefined
1681
+ ? PERPS_ERROR_CODES.BATCH_CANCEL_FAILED
1682
+ : __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mapError).call(this, new Error(statusError)).message,
1683
+ }),
1684
+ };
1685
+ });
1686
+ }
1687
+ }
1376
1688
  }
1377
1689
  catch (error) {
1378
1690
  const mappedError = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mapError).call(this, error);
1379
1691
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").logger.error(mappedError, await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getTradingErrorContext).call(this, 'cancelOrders', mappedError, {
1380
1692
  orderCount: params.length,
1381
1693
  }));
1382
- // Return all orders as failed
1383
- return {
1384
- success: false,
1385
- successCount: 0,
1386
- failureCount: params.length,
1387
- results: params.map((order) => ({
1388
- orderId: order.orderId,
1389
- symbol: order.symbol,
1390
- success: false,
1391
- error: error instanceof Error
1392
- ? mappedError.message
1393
- : PERPS_ERROR_CODES.BATCH_CANCEL_FAILED,
1394
- })),
1395
- };
1694
+ for (const result of results) {
1695
+ if (!result.success &&
1696
+ result.error === PERPS_ERROR_CODES.BATCH_CANCEL_FAILED) {
1697
+ result.error =
1698
+ error instanceof Error
1699
+ ? mappedError.message
1700
+ : PERPS_ERROR_CODES.BATCH_CANCEL_FAILED;
1701
+ }
1702
+ }
1396
1703
  }
1704
+ const successCount = results.filter(({ success }) => success).length;
1705
+ return {
1706
+ success: successCount > 0,
1707
+ successCount,
1708
+ failureCount: results.length - successCount,
1709
+ results,
1710
+ };
1397
1711
  }
1398
1712
  async closePositions(params) {
1399
1713
  // Declare outside try block so it's accessible in catch block
1400
1714
  let positionsToClose = [];
1401
1715
  try {
1402
- // Ensure provider is ready for trading (includes signing operations)
1403
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
1716
+ // Batch preparation needs trading readiness but not builder approval yet.
1717
+ // The provider-owned market policy is resolved from the positions below.
1718
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
1404
1719
  // Get all current positions from cache (avoids 429 rate limiting)
1405
1720
  const positions = await this.getPositions();
1406
1721
  // Filter positions based on params
@@ -1438,6 +1753,7 @@ export class HyperLiquidProvider {
1438
1753
  // Build orders array, plus the positions each order closes so response
1439
1754
  // statuses stay index-aligned when a position is skipped below
1440
1755
  const orders = [];
1756
+ const feePolicyContexts = [];
1441
1757
  const orderedPositions = [];
1442
1758
  // Positions no order could be built for. Reported as failures so a caller
1443
1759
  // cannot read "closed everything" from a result that left one open.
@@ -1512,6 +1828,13 @@ export class HyperLiquidProvider {
1512
1828
  r: true, // reduceOnly
1513
1829
  t: { limit: { tif: 'Ioc' } }, // Immediate or cancel for market-like execution
1514
1830
  });
1831
+ feePolicyContexts.push({
1832
+ symbol: position.symbol,
1833
+ isBuy,
1834
+ size: formattedSize,
1835
+ orderType: 'market',
1836
+ reduceOnly: true,
1837
+ });
1515
1838
  orderedPositions.push(position);
1516
1839
  orderedHip3Transfers.push(hip3Transfer);
1517
1840
  }
@@ -1526,11 +1849,19 @@ export class HyperLiquidProvider {
1526
1849
  results: skippedResults,
1527
1850
  };
1528
1851
  }
1852
+ // HyperLiquid accepts one builder context for the whole close batch.
1853
+ // Resolve every position context and charge the batch when any included
1854
+ // policy charges, preserving deterministic behavior for future policies
1855
+ // that vary by market or route.
1856
+ const chargesMetamaskBuilderFee = feePolicyContexts.some((context) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, context).chargesMetamaskBuilderFee);
1857
+ const builder = chargesMetamaskBuilderFee
1858
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this, await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: true }))
1859
+ : undefined;
1529
1860
  // Single batch API call
1530
1861
  const result = await exchangeClient.order({
1531
1862
  orders,
1532
1863
  grouping: 'na',
1533
- builder: await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this),
1864
+ ...(builder && { builder }),
1534
1865
  });
1535
1866
  // Parse response statuses (one per order)
1536
1867
  const { statuses } = result.response.data;
@@ -1644,11 +1975,11 @@ export class HyperLiquidProvider {
1644
1975
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReady).call(this);
1645
1976
  // Use live position (from WebSocket) if available, otherwise fetch via REST
1646
1977
  // Preferring WebSocket data avoids rate limiting issues with the REST API
1647
- let position = livePosition;
1648
- if (position) {
1978
+ let fetchedPosition;
1979
+ if (livePosition) {
1649
1980
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Using live position from WebSocket', {
1650
- symbol: position.symbol,
1651
- size: position.size,
1981
+ symbol: livePosition.symbol,
1982
+ size: livePosition.size,
1652
1983
  });
1653
1984
  }
1654
1985
  else {
@@ -1664,8 +1995,9 @@ export class HyperLiquidProvider {
1664
1995
  }));
1665
1996
  throw error;
1666
1997
  }
1667
- position = positions.find((pos) => pos.symbol === symbol);
1998
+ fetchedPosition = positions.find((pos) => pos.symbol === symbol);
1668
1999
  }
2000
+ const position = livePosition ?? fetchedPosition;
1669
2001
  if (!position) {
1670
2002
  throw new Error(`No position found for ${symbol}`);
1671
2003
  }
@@ -1729,17 +2061,117 @@ export class HyperLiquidProvider {
1729
2061
  error: precision.error,
1730
2062
  };
1731
2063
  }
1732
- // Everything is validated: only now run the trading setup that can prompt
1733
- // for signatures and write the referral / builder-fee approvals.
1734
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
1735
- // Cancel existing TP/SL orders for this position
1736
- // OPTIMIZATION: Use WebSocket cache first (0 weight), fall back to single-DEX REST (20 weight)
1737
- // Previously: queryUserDataAcrossDexs queried ALL DEXs (20 weight × N DEXs = 40+ weight)
1738
2064
  const assetId = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
1739
2065
  symbol,
1740
2066
  dexName,
1741
2067
  });
2068
+ const fullSize = TP_SL_CONFIG.UsePositionBoundTpsl && !isPartialTpsl
2069
+ ? '0'
2070
+ : formatHyperLiquidSize({
2071
+ size: positionSize,
2072
+ szDecimals: assetInfo.szDecimals,
2073
+ });
2074
+ // Partial TP/SL orders carry their own size; the rest cover the position.
2075
+ // A partial size that rounds away at the asset precision is rejected
2076
+ // rather than sent as '0', which the exchange reads as whole-position.
2077
+ const resolveTpslSize = (tpslSize) => tpslSize === undefined
2078
+ ? fullSize
2079
+ : formatPartialTpslSize({
2080
+ size: parseFloat(tpslSize),
2081
+ szDecimals: assetInfo.szDecimals,
2082
+ });
2083
+ // HyperLiquid accepts one builder context for the whole TP/SL action.
2084
+ // Position TP/SL has no parent policy to inherit, so resolve every
2085
+ // included trigger and charge the batch when any child policy charges.
2086
+ const feePolicyContexts = [
2087
+ ...(takeProfitPrice
2088
+ ? [
2089
+ {
2090
+ symbol,
2091
+ isBuy: !isLong,
2092
+ size: resolveTpslSize(takeProfitSize),
2093
+ orderType: 'take_profit_limit',
2094
+ price: takeProfitPrice,
2095
+ triggerPrice: takeProfitPrice,
2096
+ reduceOnly: true,
2097
+ },
2098
+ ]
2099
+ : []),
2100
+ ...(stopLossPrice
2101
+ ? [
2102
+ {
2103
+ symbol,
2104
+ isBuy: !isLong,
2105
+ size: resolveTpslSize(stopLossSize),
2106
+ orderType: 'stop_market',
2107
+ triggerPrice: stopLossPrice,
2108
+ reduceOnly: true,
2109
+ },
2110
+ ]
2111
+ : []),
2112
+ ];
2113
+ const replacementChargesMetamaskBuilderFee = feePolicyContexts.some((feePolicyContext) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, feePolicyContext)
2114
+ .chargesMetamaskBuilderFee);
2115
+ // Cancel existing TP/SL orders for this position
2116
+ // OPTIMIZATION: Use WebSocket cache first (0 weight), fall back to single-DEX REST (20 weight)
2117
+ // Previously: queryUserDataAcrossDexs queried ALL DEXs (20 weight × N DEXs = 40+ weight)
1742
2118
  let cancelRequests = [];
2119
+ const restorablePositionTpslOrders = [];
2120
+ const restorableStandaloneTpslOrders = [];
2121
+ // Every replacement must cancel first so old and new reduce-only triggers
2122
+ // cannot execute together. Retain the exact trigger definitions so a
2123
+ // definitively failed replacement can restore confirmed cancellations.
2124
+ const captureRestorableTpslOrders = (tpslOrders) => {
2125
+ if (!takeProfitPrice && !stopLossPrice) {
2126
+ return;
2127
+ }
2128
+ tpslOrders.forEach((order) => {
2129
+ const orderId = Number(order.orderId);
2130
+ const orderType = order.triggerOrderType;
2131
+ const triggerPrice = order.triggerPrice ?? order.price;
2132
+ if (!Number.isSafeInteger(orderId) || !orderType || !triggerPrice) {
2133
+ throw new Error(PERPS_ERROR_CODES.TPSL_UPDATE_FAILED);
2134
+ }
2135
+ const size = order.isPositionTpsl ? '0' : order.remainingSize;
2136
+ const isBuy = order.side === 'buy';
2137
+ const feePolicyContext = {
2138
+ symbol,
2139
+ isBuy,
2140
+ size,
2141
+ orderType,
2142
+ triggerPrice,
2143
+ reduceOnly: true,
2144
+ ...(isLimitExecutionOrderType(orderType) && {
2145
+ price: order.price,
2146
+ }),
2147
+ };
2148
+ const restorableOrder = {
2149
+ orderId,
2150
+ order: {
2151
+ a: assetId,
2152
+ b: isBuy,
2153
+ p: order.price,
2154
+ s: size,
2155
+ r: true,
2156
+ t: {
2157
+ trigger: {
2158
+ isMarket: !isLimitExecutionOrderType(orderType),
2159
+ triggerPx: triggerPrice,
2160
+ tpsl: getTriggerDirection(orderType) === 'stop' ? 'sl' : 'tp',
2161
+ },
2162
+ },
2163
+ },
2164
+ chargesMetamaskBuilderFee: __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, feePolicyContext)
2165
+ .chargesMetamaskBuilderFee,
2166
+ };
2167
+ if (order.isPositionTpsl) {
2168
+ restorablePositionTpslOrders.push(restorableOrder);
2169
+ }
2170
+ else {
2171
+ restorableStandaloneTpslOrders.push(restorableOrder);
2172
+ }
2173
+ });
2174
+ };
1743
2175
  // Use atomic getter to prevent race condition between check and get
1744
2176
  const cachedOrders = __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").getOrdersCacheIfInitialized();
1745
2177
  // Replacing TP/SL has to consider standalone ('na' grouping) triggers —
@@ -1766,16 +2198,16 @@ export class HyperLiquidProvider {
1766
2198
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(cachedOrders === null
1767
2199
  ? 'WebSocket cache not initialized, falling back to single-DEX REST query'
1768
2200
  : 'TP/SL update needs parent/child order context: using single-DEX REST query', { dex: dexName ?? 'main', isPartialTpsl });
1769
- const orders = await infoClient.frontendOpenOrders({
2201
+ const openOrders = await infoClient.frontendOpenOrders({
1770
2202
  user: userAddress,
1771
2203
  dex: dexName ?? undefined,
1772
2204
  });
1773
2205
  // Orders that belong to a pending parent order (normalTpsl children) are
1774
2206
  // also listed at the top level, so collect their IDs to exclude them:
1775
2207
  // they protect that pending order, not this position.
1776
- const childOrderIds = collectChildOrderIds(orders);
2208
+ const childOrderIds = collectChildOrderIds(openOrders);
1777
2209
  // Filter using raw SDK response properties
1778
- const tpslOrders = orders.filter((order) => order.coin === symbol &&
2210
+ const tpslOrders = openOrders.filter((order) => order.coin === symbol &&
1779
2211
  order.reduceOnly &&
1780
2212
  // Position-bound TP/SL always qualifies, and so do standalone
1781
2213
  // triggers on this market (they belong to the position too, whether
@@ -1787,6 +2219,7 @@ export class HyperLiquidProvider {
1787
2219
  order.isTrigger &&
1788
2220
  (order.orderType.includes('Take Profit') ||
1789
2221
  order.orderType.includes('Stop')));
2222
+ captureRestorableTpslOrders(tpslOrders.map((order) => adaptOrderFromSDK(order, position)));
1790
2223
  cancelRequests = tpslOrders.map((order) => ({
1791
2224
  a: assetId,
1792
2225
  o: order.oid,
@@ -1810,36 +2243,14 @@ export class HyperLiquidProvider {
1810
2243
  order.detailedOrderType &&
1811
2244
  (order.detailedOrderType.includes('Take Profit') ||
1812
2245
  order.detailedOrderType.includes('Stop')));
2246
+ captureRestorableTpslOrders(tpslOrders);
1813
2247
  cancelRequests = tpslOrders.map((order) => ({
1814
2248
  a: assetId,
1815
2249
  o: parseInt(order.orderId, 10),
1816
2250
  }));
1817
2251
  }
1818
- if (cancelRequests.length > 0) {
1819
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`Canceling ${cancelRequests.length} existing TP/SL orders for ${symbol}`);
1820
- const cancelResult = await exchangeClient.cancel({
1821
- cancels: cancelRequests,
1822
- });
1823
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Cancel result:', cancelResult);
1824
- }
1825
- // assetId already validated above when building cancelRequests
1826
2252
  // Build orders array for TP/SL
1827
2253
  const orders = [];
1828
- const fullSize = TP_SL_CONFIG.UsePositionBoundTpsl && !isPartialTpsl
1829
- ? '0'
1830
- : formatHyperLiquidSize({
1831
- size: positionSize,
1832
- szDecimals: assetInfo.szDecimals,
1833
- });
1834
- // Partial TP/SL orders carry their own size; the rest cover the position.
1835
- // A partial size that rounds away at the asset precision is rejected
1836
- // rather than sent as '0', which the exchange reads as whole-position.
1837
- const resolveTpslSize = (tpslSize) => tpslSize === undefined
1838
- ? fullSize
1839
- : formatPartialTpslSize({
1840
- size: parseFloat(tpslSize),
1841
- szDecimals: assetInfo.szDecimals,
1842
- });
1843
2254
  // Take Profit order
1844
2255
  if (takeProfitPrice) {
1845
2256
  const tpOrder = {
@@ -1888,24 +2299,184 @@ export class HyperLiquidProvider {
1888
2299
  };
1889
2300
  orders.push(slOrder);
1890
2301
  }
1891
- // If no new orders, we've just cancelled existing ones (clearing TP/SL)
2302
+ const rollbackRequiresBuilderFee = [
2303
+ ...restorablePositionTpslOrders,
2304
+ ...restorableStandaloneTpslOrders,
2305
+ ].some((order) => order.chargesMetamaskBuilderFee);
2306
+ const requiresBuilderFee = replacementChargesMetamaskBuilderFee || rollbackRequiresBuilderFee;
2307
+ // Approval and builder-context resolution both finish before the
2308
+ // pre-cancel. A failure here therefore leaves the old protection intact.
2309
+ const builderFeeSetupContext = requiresBuilderFee
2310
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, {
2311
+ requiresBuilderFee: true,
2312
+ builderFeeApprovalFailureCode: PERPS_ERROR_CODES.TPSL_UPDATE_FAILED,
2313
+ })
2314
+ : await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
2315
+ const builderOrderContext = builderFeeSetupContext
2316
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this, builderFeeSetupContext)
2317
+ : undefined;
2318
+ const restoreCancelledProtection = async (cancelledOrderIds) => {
2319
+ const restoredOrderIds = [];
2320
+ let success = true;
2321
+ const protectionGroups = [
2322
+ {
2323
+ grouping: 'positionTpsl',
2324
+ entries: restorablePositionTpslOrders,
2325
+ },
2326
+ {
2327
+ grouping: 'na',
2328
+ entries: restorableStandaloneTpslOrders,
2329
+ },
2330
+ ];
2331
+ for (const protection of protectionGroups) {
2332
+ const entries = protection.entries.filter((entry) => cancelledOrderIds.has(entry.orderId));
2333
+ if (entries.length === 0) {
2334
+ continue;
2335
+ }
2336
+ try {
2337
+ const result = await exchangeClient.order({
2338
+ orders: entries.map((entry) => entry.order),
2339
+ grouping: protection.grouping,
2340
+ ...(entries.some((entry) => entry.chargesMetamaskBuilderFee) &&
2341
+ builderOrderContext && { builder: builderOrderContext }),
2342
+ });
2343
+ const statuses = result.response?.data?.statuses ?? [];
2344
+ const outcomes = statuses
2345
+ .slice(0, entries.length)
2346
+ .map((status) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderPlacementOutcome).call(this, status));
2347
+ restoredOrderIds.push(...outcomes.flatMap((outcome) => outcome ? [outcome.orderId] : []));
2348
+ if (result.status !== 'ok' ||
2349
+ statuses.length !== entries.length ||
2350
+ outcomes.some((outcome) => outcome === undefined)) {
2351
+ success = false;
2352
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").logger.error(new Error(PERPS_ERROR_CODES.TPSL_UPDATE_FAILED), __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getErrorContext).call(this, 'updatePositionTPSL > restoreCancelledProtection', { symbol, grouping: protection.grouping, statuses }));
2353
+ }
2354
+ }
2355
+ catch (error) {
2356
+ success = false;
2357
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").logger.error(ensureError(error, 'HyperLiquidProvider.updatePositionTPSL.restoreCancelledProtection'), __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getErrorContext).call(this, 'updatePositionTPSL > restoreCancelledProtection', { symbol, grouping: protection.grouping }));
2358
+ }
2359
+ }
2360
+ return { restoredOrderIds, success };
2361
+ };
2362
+ const createProtectionLostResult = (survivingOrderIds) => createErrorResult(new Error(PERPS_ERROR_CODES.TPSL_PROTECTION_LOST), {
2363
+ success: false,
2364
+ childOrderIds: [...new Set(survivingOrderIds)],
2365
+ });
2366
+ // Clearing has no replacement batch to preserve. A partial cancellation
2367
+ // is reported so the caller can retry the same clear operation.
1892
2368
  if (orders.length === 0) {
2369
+ const remainingOrderIds = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequests).call(this, exchangeClient, cancelRequests);
2370
+ if (remainingOrderIds.length > 0) {
2371
+ throw new Error(PERPS_ERROR_CODES.TPSL_UPDATE_FAILED);
2372
+ }
1893
2373
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('No new TP/SL orders to place - existing ones cancelled');
1894
2374
  return {
1895
2375
  success: true,
1896
2376
  // No orderId since we only cancelled orders, didn't place new ones
1897
2377
  };
1898
2378
  }
1899
- // Submit via SDK exchange client. Position-bound TP/SL uses 'positionTpsl';
1900
- // partial TP/SL must be standalone reduce-only triggers ('na'), since a
1901
- // position-bound TP/SL always closes the whole position.
1902
- const result = await exchangeClient.order({
1903
- orders,
1904
- grouping: isPartialTpsl ? 'na' : 'positionTpsl',
1905
- builder: await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this),
1906
- });
1907
- if (result.status !== 'ok') {
1908
- throw new Error(`TP/SL update failed: ${JSON.stringify(result)}`);
2379
+ // Cancel before placing for both position-bound and standalone partial
2380
+ // triggers. A place-first partial update leaves both trigger sets live
2381
+ // during the cancellation round trip and can reduce more than requested.
2382
+ const confirmedCancelledOldOrderIds = new Set();
2383
+ const oldCancellation = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequestBatch).call(this, exchangeClient, cancelRequests);
2384
+ if (!oldCancellation.responseComplete) {
2385
+ const requestedOrderIds = new Set(cancelRequests.map((request) => request.o));
2386
+ let possiblyLiveOrderIds = Array.from(requestedOrderIds, String);
2387
+ try {
2388
+ const liveOrders = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchOpenOrders).call(this, { dexName });
2389
+ possiblyLiveOrderIds = liveOrders
2390
+ .filter((order) => requestedOrderIds.has(order.oid))
2391
+ .map((order) => String(order.oid));
2392
+ }
2393
+ catch (error) {
2394
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Could not reconcile TP/SL protection after an incomplete cancel', {
2395
+ symbol,
2396
+ error: ensureError(error, 'HyperLiquidProvider.updatePositionTPSL.reconcileProtection').message,
2397
+ });
2398
+ }
2399
+ return createProtectionLostResult(possiblyLiveOrderIds);
2400
+ }
2401
+ oldCancellation.cancelledOrderIds.forEach((orderId) => confirmedCancelledOldOrderIds.add(orderId));
2402
+ if (oldCancellation.remainingOrderIds.length > 0) {
2403
+ const restoration = await restoreCancelledProtection(confirmedCancelledOldOrderIds);
2404
+ if (!restoration.success) {
2405
+ return createProtectionLostResult([
2406
+ ...oldCancellation.remainingOrderIds.map(String),
2407
+ ...restoration.restoredOrderIds,
2408
+ ]);
2409
+ }
2410
+ const updateError = new Error(PERPS_ERROR_CODES.TPSL_UPDATE_FAILED);
2411
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").logger.error(updateError, __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getErrorContext).call(this, 'updatePositionTPSL', {
2412
+ symbol: params.symbol,
2413
+ hasTakeProfit: params.takeProfitPrice !== undefined,
2414
+ hasStopLoss: params.stopLossPrice !== undefined,
2415
+ }));
2416
+ return createErrorResult(updateError, {
2417
+ success: false,
2418
+ childOrderIds: [
2419
+ ...new Set([
2420
+ ...oldCancellation.remainingOrderIds.map(String),
2421
+ ...restoration.restoredOrderIds,
2422
+ ]),
2423
+ ],
2424
+ });
2425
+ }
2426
+ let result;
2427
+ try {
2428
+ result = await exchangeClient.order({
2429
+ orders,
2430
+ grouping: isPartialTpsl ? 'na' : 'positionTpsl',
2431
+ ...(replacementChargesMetamaskBuilderFee &&
2432
+ builderOrderContext && { builder: builderOrderContext }),
2433
+ });
2434
+ }
2435
+ catch (error) {
2436
+ const restoration = await restoreCancelledProtection(confirmedCancelledOldOrderIds);
2437
+ if (!restoration.success) {
2438
+ return createProtectionLostResult(restoration.restoredOrderIds);
2439
+ }
2440
+ throw error;
2441
+ }
2442
+ const placementStatuses = result.response?.data?.statuses ?? [];
2443
+ const placementOutcomes = placementStatuses
2444
+ .slice(0, orders.length)
2445
+ .map((status) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderPlacementOutcome).call(this, status));
2446
+ const replacementOrderIds = placementOutcomes.flatMap((outcome) => outcome ? [outcome.orderId] : []);
2447
+ const restingReplacementOrderIds = placementOutcomes.flatMap((outcome) => outcome?.state === 'resting' ? [outcome.orderId] : []);
2448
+ const filledReplacementOrderIds = placementOutcomes.flatMap((outcome) => outcome?.state === 'filled' ? [outcome.orderId] : []);
2449
+ if (result.status !== 'ok' ||
2450
+ placementStatuses.length !== orders.length ||
2451
+ replacementOrderIds.length !== orders.length) {
2452
+ const remainingReplacementIds = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequests).call(this, exchangeClient, restingReplacementOrderIds.map((orderId) => ({
2453
+ a: assetId,
2454
+ o: Number(orderId),
2455
+ })));
2456
+ const recoverableOrderIds = [
2457
+ ...filledReplacementOrderIds,
2458
+ ...remainingReplacementIds.map(String),
2459
+ ];
2460
+ if (recoverableOrderIds.length === 0) {
2461
+ const restoration = await restoreCancelledProtection(confirmedCancelledOldOrderIds);
2462
+ if (!restoration.success) {
2463
+ return createProtectionLostResult(restoration.restoredOrderIds);
2464
+ }
2465
+ }
2466
+ // A filled replacement may have changed or closed the position, while
2467
+ // an uncancelled replacement may still protect it. Restoring the old
2468
+ // whole-position triggers in either case risks stale or duplicate
2469
+ // protection, so return those IDs for caller reconciliation instead.
2470
+ if (recoverableOrderIds.length > 0) {
2471
+ if (!isPartialTpsl) {
2472
+ return createProtectionLostResult(recoverableOrderIds);
2473
+ }
2474
+ return createErrorResult(new Error(PERPS_ERROR_CODES.TPSL_UPDATE_FAILED), {
2475
+ success: false,
2476
+ childOrderIds: recoverableOrderIds,
2477
+ });
2478
+ }
2479
+ throw new Error(PERPS_ERROR_CODES.TPSL_UPDATE_FAILED);
1909
2480
  }
1910
2481
  return {
1911
2482
  success: true,
@@ -1933,8 +2504,9 @@ export class HyperLiquidProvider {
1933
2504
  async closePosition(params) {
1934
2505
  try {
1935
2506
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Closing position:', params);
1936
- // Ensure provider is ready for trading (includes signing operations)
1937
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
2507
+ // The delegated placeOrder call resolves the builder-fee policy for the
2508
+ // concrete close order after validation.
2509
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
1938
2510
  // Use provided position (from WebSocket) or fetch from cache
1939
2511
  // This avoids unnecessary API calls and prevents 429 rate limiting
1940
2512
  let { position } = params;
@@ -2734,6 +3306,7 @@ export class HyperLiquidProvider {
2734
3306
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Using cached open orders from WebSocket', {
2735
3307
  count: cachedOrders.length,
2736
3308
  });
3309
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_restoreScaleOrderGroups).call(this, cachedOrders);
2737
3310
  return cachedOrders;
2738
3311
  }
2739
3312
  }
@@ -2764,6 +3337,7 @@ export class HyperLiquidProvider {
2764
3337
  const position = positions.find((pos) => pos.symbol === order.coin);
2765
3338
  return adaptOrderFromSDK(order, position);
2766
3339
  });
3340
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_restoreScaleOrderGroups).call(this, orders);
2767
3341
  return orders;
2768
3342
  }
2769
3343
  catch (error) {
@@ -3317,6 +3891,7 @@ export class HyperLiquidProvider {
3317
3891
  * @returns A promise that resolves to the combined market data from all enabled DEXs.
3318
3892
  */
3319
3893
  async getMarketDataWithPrices() {
3894
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
3320
3895
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Getting market data with prices via HyperLiquid SDK');
3321
3896
  // Ensure asset mapping is built first (populates meta cache)
3322
3897
  // This guarantees buildAssetMapping has run before we check cache,
@@ -3332,16 +3907,15 @@ export class HyperLiquidProvider {
3332
3907
  // Check the meta cache first to avoid redundant API calls when buildAssetMapping
3333
3908
  // has already populated it; on cache miss, delegate to #fetchSingleDexFresh.
3334
3909
  const dexDataResults = await Promise.all(enabledDexs.map(async (dex) => {
3335
- const dexKey = dex ?? '';
3336
3910
  const dexParam = dex ?? '';
3337
- const cachedMeta = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dexKey);
3911
+ const cachedMeta = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dex);
3338
3912
  if (!cachedMeta) {
3339
3913
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`[getMarketDataWithPrices] Cache miss for ${dex ?? 'main'}, fetching`);
3340
- return __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchSingleDexFresh).call(this, infoClient, dex);
3914
+ return __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchSingleDexFresh).call(this, infoClient, dex, lifecycleGeneration);
3341
3915
  }
3342
3916
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`[getMarketDataWithPrices] Using cached meta for ${dex ?? 'main'}`, { universeSize: cachedMeta.universe.length });
3343
3917
  let metaForDex = cachedMeta;
3344
- let assetCtxs = __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").getDexAssetCtxsCache(dexKey) ?? [];
3918
+ let assetCtxs = __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").getDexAssetCtxsCache(dexParam) ?? [];
3345
3919
  let failedStep;
3346
3920
  let errorMessage;
3347
3921
  if (assetCtxs.length !== metaForDex.universe.length) {
@@ -3362,10 +3936,12 @@ export class HyperLiquidProvider {
3362
3936
  }
3363
3937
  metaForDex = freshMeta;
3364
3938
  assetCtxs = freshAssetCtxs;
3365
- __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").set(dexKey, freshMeta);
3366
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexMetaCache(dexKey, freshMeta);
3367
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexAssetCtxsCache(dexKey, assetCtxs);
3368
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dex, freshMeta);
3939
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheDexMetadataFromRead).call(this, {
3940
+ dex,
3941
+ meta: freshMeta,
3942
+ assetCtxs,
3943
+ lifecycleGeneration,
3944
+ });
3369
3945
  }
3370
3946
  catch (error) {
3371
3947
  return {
@@ -3417,7 +3993,7 @@ export class HyperLiquidProvider {
3417
3993
  const retryDelayMs = 2000;
3418
3994
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`[getMarketDataWithPrices] All DEXs returned empty, retrying in ${retryDelayMs}ms`);
3419
3995
  await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
3420
- const retryResults = await Promise.all(enabledDexs.map((dex) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchSingleDexFresh).call(this, infoClient, dex)));
3996
+ const retryResults = await Promise.all(enabledDexs.map((dex) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchSingleDexFresh).call(this, infoClient, dex, lifecycleGeneration)));
3421
3997
  const usdcFilteredRetryResults = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_excludeNonUsdcCollateralResults).call(this, retryResults);
3422
3998
  __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mergeDexResultsInto).call(this, usdcFilteredRetryResults, combinedUniverse, combinedAssetCtxs, combinedAllMids);
3423
3999
  latestDexResults = usdcFilteredRetryResults;
@@ -3477,6 +4053,9 @@ export class HyperLiquidProvider {
3477
4053
  assetCtxs: combinedAssetCtxs,
3478
4054
  allMids: combinedAllMids,
3479
4055
  }, __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").marketDataFormatters, HIP3_ASSET_MARKET_TYPES, HYPERLIQUID_ASSET_NAMES);
4056
+ if (!__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Market data cache write')) {
4057
+ return transformedMarketData;
4058
+ }
3480
4059
  return __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheFreshMarketDataSnapshot).call(this, transformedMarketData, latestDexResults);
3481
4060
  }
3482
4061
  /**
@@ -4131,23 +4710,28 @@ export class HyperLiquidProvider {
4131
4710
  async toggleTestnet() {
4132
4711
  try {
4133
4712
  const newIsTestnet = !__classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode();
4134
- // Await pending initialization to prevent race condition where
4135
- // the IIFE sets clientsInitialized = true after we reset it
4136
- const pendingInit = __classPrivateFieldGet(this, _HyperLiquidProvider_initializationPromise, "f");
4137
- __classPrivateFieldSet(this, _HyperLiquidProvider_initializationPromise, null, "f");
4138
- if (pendingInit) {
4139
- try {
4140
- await pendingInit;
4141
- }
4142
- catch {
4143
- // Ignore - we're switching networks anyway
4144
- }
4713
+ const disconnectResult = await this.disconnect();
4714
+ if (!disconnectResult.success) {
4715
+ // The network did not change. Keep the provider eligible for lazy
4716
+ // recovery instead of leaving this aborted toggle permanently sticky.
4717
+ __classPrivateFieldSet(this, _HyperLiquidProvider_isDisconnected, false, "f");
4718
+ throw new Error(disconnectResult.error ??
4719
+ 'Failed to disconnect before network toggle');
4145
4720
  }
4146
4721
  // Update all services
4147
4722
  __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").setTestnetMode(newIsTestnet);
4723
+ // Invalidate reads that started after disconnect completed but before
4724
+ // the clients switched networks.
4725
+ __classPrivateFieldSet(this, _HyperLiquidProvider_lifecycleGeneration, __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f") + 1, "f");
4148
4726
  __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").setTestnetMode(newIsTestnet);
4149
- // Reset initialization flag so clients will be recreated on next use
4150
- __classPrivateFieldSet(this, _HyperLiquidProvider_clientsInitialized, false, "f");
4727
+ const initializeResult = await this.initialize();
4728
+ if (!initializeResult.success) {
4729
+ // The network switch is already committed. Leave the provider eligible
4730
+ // for lazy initialization so the next operation can retry the failed
4731
+ // client setup instead of remaining permanently disconnected.
4732
+ __classPrivateFieldSet(this, _HyperLiquidProvider_isDisconnected, false, "f");
4733
+ throw new Error(initializeResult.error ?? 'Failed to initialize after network toggle');
4734
+ }
4151
4735
  return {
4152
4736
  success: true,
4153
4737
  isTestnet: newIsTestnet,
@@ -4166,9 +4750,18 @@ export class HyperLiquidProvider {
4166
4750
  * @returns A promise that resolves to the result.
4167
4751
  */
4168
4752
  async initialize() {
4753
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
4169
4754
  try {
4755
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0) {
4756
+ throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
4757
+ }
4170
4758
  // Ensure clients are initialized (lazy initialization)
4171
4759
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureClientsInitialized).call(this);
4760
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0 ||
4761
+ lifecycleGeneration !== __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f")) {
4762
+ throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
4763
+ }
4764
+ __classPrivateFieldSet(this, _HyperLiquidProvider_isDisconnected, false, "f");
4172
4765
  return {
4173
4766
  success: true,
4174
4767
  chainId: getChainId(__classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode()),
@@ -4306,6 +4899,7 @@ export class HyperLiquidProvider {
4306
4899
  * @returns A promise that resolves to the numeric result.
4307
4900
  */
4308
4901
  async getMaxLeverage(asset) {
4902
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
4309
4903
  try {
4310
4904
  // Check cache first
4311
4905
  const cached = __classPrivateFieldGet(this, _HyperLiquidProvider_maxLeverageCache, "f").get(asset);
@@ -4338,11 +4932,12 @@ export class HyperLiquidProvider {
4338
4932
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`Asset ${asset} not found in universe, using default max leverage`);
4339
4933
  return PERPS_CONSTANTS.DefaultMaxLeverage;
4340
4934
  }
4341
- // Cache the result
4342
- __classPrivateFieldGet(this, _HyperLiquidProvider_maxLeverageCache, "f").set(asset, {
4343
- value: assetInfo.maxLeverage,
4344
- timestamp: now,
4345
- });
4935
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Maximum leverage cache write')) {
4936
+ __classPrivateFieldGet(this, _HyperLiquidProvider_maxLeverageCache, "f").set(asset, {
4937
+ value: assetInfo.maxLeverage,
4938
+ timestamp: now,
4939
+ });
4940
+ }
4346
4941
  return assetInfo.maxLeverage;
4347
4942
  }
4348
4943
  catch (error) {
@@ -4363,7 +4958,13 @@ export class HyperLiquidProvider {
4363
4958
  * @returns A promise that resolves to the result.
4364
4959
  */
4365
4960
  async calculateFees(params) {
4961
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
4366
4962
  const { orderType, isMaker = false, amount, symbol } = params;
4963
+ const numericAmount = amount === undefined ? undefined : Number.parseFloat(amount);
4964
+ const parsedAmount = numericAmount === undefined ||
4965
+ (Number.isFinite(numericAmount) && numericAmount >= 0)
4966
+ ? numericAmount
4967
+ : 0;
4367
4968
  // Every placement is charged as its execution kind: a stop_market fills as a
4368
4969
  // market order (taker), a stop_limit as a limit order, a scale ladder as the
4369
4970
  // resting limit orders it fans out into, and a TWAP as the marketable
@@ -4410,6 +5011,7 @@ export class HyperLiquidProvider {
4410
5011
  // Try to get user-specific rates if wallet is connected
4411
5012
  try {
4412
5013
  const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
5014
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'User fee cache read');
4413
5015
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('User Address Retrieved', {
4414
5016
  userAddress,
4415
5017
  network: __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode() ? 'testnet' : 'mainnet',
@@ -4450,7 +5052,9 @@ export class HyperLiquidProvider {
4450
5052
  });
4451
5053
  // Fetch fresh rates from SDK
4452
5054
  // Read-only operation: only need client initialization
5055
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'User fee fetch');
4453
5056
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureClientsInitialized).call(this);
5057
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'User fee fetch');
4454
5058
  __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").ensureInitialized();
4455
5059
  const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient();
4456
5060
  const userFees = await infoClient.userFees({
@@ -4463,13 +5067,21 @@ export class HyperLiquidProvider {
4463
5067
  activeStakingDiscount: userFees.activeStakingDiscount,
4464
5068
  });
4465
5069
  // Parse base user rates (these don't include discounts as expected)
4466
- const baseUserTakerRate = parseFloat(userFees.userCrossRate);
4467
- const baseUserMakerRate = parseFloat(userFees.userAddRate);
4468
- const baseUserSpotTakerRate = parseFloat(userFees.userSpotCrossRate);
4469
- const baseUserSpotMakerRate = parseFloat(userFees.userSpotAddRate);
5070
+ const baseUserTakerRate = parseBoundedNonNegativeDecimal(userFees.userCrossRate, MAX_API_FEE_RATE);
5071
+ const baseUserMakerRate = parseBoundedNonNegativeDecimal(userFees.userAddRate, MAX_API_FEE_RATE);
5072
+ const baseUserSpotTakerRate = parseBoundedNonNegativeDecimal(userFees.userSpotCrossRate, MAX_API_FEE_RATE);
5073
+ const baseUserSpotMakerRate = parseBoundedNonNegativeDecimal(userFees.userSpotAddRate, MAX_API_FEE_RATE);
4470
5074
  // Apply discounts manually since HyperLiquid API doesn't apply them
4471
- const referralDiscount = parseFloat(userFees.activeReferralDiscount || '0');
4472
- const stakingDiscount = parseFloat(userFees.activeStakingDiscount?.discount || '0');
5075
+ const referralDiscount = parseBoundedNonNegativeDecimal(userFees.activeReferralDiscount || '0', MAX_API_FEE_DISCOUNT);
5076
+ const stakingDiscount = parseBoundedNonNegativeDecimal(userFees.activeStakingDiscount?.discount || '0', MAX_API_FEE_DISCOUNT);
5077
+ if (baseUserTakerRate === null ||
5078
+ baseUserMakerRate === null ||
5079
+ baseUserSpotTakerRate === null ||
5080
+ baseUserSpotMakerRate === null ||
5081
+ referralDiscount === null ||
5082
+ stakingDiscount === null) {
5083
+ throw new Error('Invalid fee rates received from API');
5084
+ }
4473
5085
  // Calculate total discount (referral + staking, but not compounding)
4474
5086
  const totalDiscount = Math.min(referralDiscount + stakingDiscount, 0.4); // Cap at 40%
4475
5087
  // Apply discount to rates
@@ -4495,20 +5107,20 @@ export class HyperLiquidProvider {
4495
5107
  },
4496
5108
  });
4497
5109
  // Validate all rates are valid numbers before caching
4498
- if (isNaN(perpsTakerRate) ||
4499
- isNaN(perpsMakerRate) ||
4500
- isNaN(spotTakerRate) ||
4501
- isNaN(spotMakerRate) ||
5110
+ if (!Number.isFinite(perpsTakerRate) ||
5111
+ !Number.isFinite(perpsMakerRate) ||
5112
+ !Number.isFinite(spotTakerRate) ||
5113
+ !Number.isFinite(spotMakerRate) ||
4502
5114
  perpsTakerRate < 0 ||
4503
5115
  perpsMakerRate < 0 ||
4504
5116
  spotTakerRate < 0 ||
4505
5117
  spotMakerRate < 0) {
4506
5118
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Fee Rate Validation Failed', {
4507
5119
  validation: {
4508
- perpsTakerValid: !isNaN(perpsTakerRate) && perpsTakerRate >= 0,
4509
- perpsMakerValid: !isNaN(perpsMakerRate) && perpsMakerRate >= 0,
4510
- spotTakerValid: !isNaN(spotTakerRate) && spotTakerRate >= 0,
4511
- spotMakerValid: !isNaN(spotMakerRate) && spotMakerRate >= 0,
5120
+ perpsTakerValid: Number.isFinite(perpsTakerRate) && perpsTakerRate >= 0,
5121
+ perpsMakerValid: Number.isFinite(perpsMakerRate) && perpsMakerRate >= 0,
5122
+ spotTakerValid: Number.isFinite(spotTakerRate) && spotTakerRate >= 0,
5123
+ spotMakerValid: Number.isFinite(spotMakerRate) && spotMakerRate >= 0,
4512
5124
  },
4513
5125
  rawValues: {
4514
5126
  perpsTakerRate,
@@ -4527,6 +5139,7 @@ export class HyperLiquidProvider {
4527
5139
  timestamp: Date.now(),
4528
5140
  ttl: 5 * 60 * 1000, // 5 minutes
4529
5141
  };
5142
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'User fee cache write');
4530
5143
  __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeCache, "f").set(userAddress, rates);
4531
5144
  // Same maker/taker decision as the base rates above, chase included.
4532
5145
  let userFeeRate = chargesMakerRate
@@ -4550,6 +5163,9 @@ export class HyperLiquidProvider {
4550
5163
  catch (error) {
4551
5164
  // Silently fall back to base rates
4552
5165
  const safeError = ensureError(error, 'HyperLiquidProvider.getFeeSchedule');
5166
+ if (safeError.message === PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE) {
5167
+ throw safeError;
5168
+ }
4553
5169
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Fee API Call Failed - Falling Back to Base Rates', {
4554
5170
  error: safeError.message,
4555
5171
  errorType: safeError.name,
@@ -4558,43 +5174,46 @@ export class HyperLiquidProvider {
4558
5174
  userAddress: 'unknown',
4559
5175
  });
4560
5176
  }
4561
- const parsedAmount = amount ? parseFloat(amount) : 0;
5177
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Fee calculation');
4562
5178
  // Protocol base fee (HyperLiquid's fee)
4563
5179
  const protocolFeeRate = feeRate;
4564
- let protocolFeeAmount;
4565
- if (amount === undefined) {
4566
- protocolFeeAmount = undefined;
4567
- }
4568
- else if (isNaN(parsedAmount)) {
4569
- protocolFeeAmount = 0;
4570
- }
4571
- else {
4572
- protocolFeeAmount = parsedAmount * protocolFeeRate;
4573
- }
4574
- // MetaMask builder fee (0.1% = 0.001) with optional reward discount
4575
- let metamaskFeeRate = BUILDER_FEE_CONFIG.MaxFeeDecimal;
4576
- // Apply MetaMask reward discount if active
4577
- if (__classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") !== undefined) {
4578
- const discount = __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") / BASIS_POINTS_DIVISOR; // Convert basis points to decimal
4579
- metamaskFeeRate = BUILDER_FEE_CONFIG.MaxFeeDecimal * (1 - discount);
5180
+ const protocolFeeAmount = parsedAmount === undefined ? undefined : parsedAmount * protocolFeeRate;
5181
+ // The provider policy, not the client, owns whether this placement can
5182
+ // carry a builder fee. The dedicated TWAP action has no builder field.
5183
+ const { chargesMetamaskBuilderFee } = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, params);
5184
+ const baseMetamaskFeeRate = chargesMetamaskBuilderFee
5185
+ ? BUILDER_FEE_CONFIG.MaxFeeDecimal
5186
+ : 0;
5187
+ const metamaskFeeDiscount = __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") === undefined
5188
+ ? 0
5189
+ : __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") / BASIS_POINTS_DIVISOR;
5190
+ const metamaskFeeRate = baseMetamaskFeeRate * (1 - metamaskFeeDiscount);
5191
+ if (chargesMetamaskBuilderFee && __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") !== undefined) {
4580
5192
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Applied MetaMask fee discount', {
4581
- originalRate: BUILDER_FEE_CONFIG.MaxFeeDecimal,
5193
+ originalRate: baseMetamaskFeeRate,
4582
5194
  discountBips: __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f"),
4583
5195
  discountPercentage: __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") / 100,
4584
5196
  adjustedRate: metamaskFeeRate,
4585
- discountAmount: BUILDER_FEE_CONFIG.MaxFeeDecimal * discount,
5197
+ discountAmount: baseMetamaskFeeRate * metamaskFeeDiscount,
4586
5198
  });
4587
5199
  }
4588
- const validAmountForMetamaskFee = isNaN(parsedAmount)
4589
- ? 0
4590
- : parsedAmount * metamaskFeeRate;
4591
- const metamaskFeeAmount = amount === undefined ? undefined : validAmountForMetamaskFee;
5200
+ const metamaskFeeAmount = parsedAmount === undefined ? undefined : parsedAmount * metamaskFeeRate;
4592
5201
  // Total fees
4593
5202
  const totalFeeRate = protocolFeeRate + metamaskFeeRate;
4594
- const validAmountForTotalFee = isNaN(parsedAmount)
4595
- ? 0
4596
- : parsedAmount * totalFeeRate;
4597
- const totalFeeAmount = amount === undefined ? undefined : validAmountForTotalFee;
5203
+ const totalFeeAmount = parsedAmount === undefined ? undefined : parsedAmount * totalFeeRate;
5204
+ if (!Number.isFinite(protocolFeeRate) ||
5205
+ protocolFeeRate < 0 ||
5206
+ !Number.isFinite(metamaskFeeRate) ||
5207
+ metamaskFeeRate < 0 ||
5208
+ !Number.isFinite(totalFeeRate) ||
5209
+ totalFeeRate < 0 ||
5210
+ (protocolFeeAmount !== undefined &&
5211
+ !Number.isFinite(protocolFeeAmount)) ||
5212
+ (metamaskFeeAmount !== undefined &&
5213
+ !Number.isFinite(metamaskFeeAmount)) ||
5214
+ (totalFeeAmount !== undefined && !Number.isFinite(totalFeeAmount))) {
5215
+ throw new Error('Invalid fee calculation result');
5216
+ }
4598
5217
  const result = {
4599
5218
  // Total fees
4600
5219
  feeRate: totalFeeRate,
@@ -4653,100 +5272,18 @@ export class HyperLiquidProvider {
4653
5272
  * @returns A promise that resolves to the result.
4654
5273
  */
4655
5274
  async disconnect() {
4656
- __classPrivateFieldSet(this, _HyperLiquidProvider_chasePlacementBlockers, __classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") + 1, "f");
4657
- __classPrivateFieldSet(this, _HyperLiquidProvider_chaseGeneration, __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f") + 1, "f");
5275
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationPromise, "f")) {
5276
+ return __classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationPromise, "f");
5277
+ }
5278
+ const operation = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_performDisconnect).call(this);
5279
+ __classPrivateFieldSet(this, _HyperLiquidProvider_disconnectOperationPromise, operation, "f");
4658
5280
  try {
4659
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Disconnecting provider', {
4660
- isTestnet: __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode(),
4661
- timestamp: new Date().toISOString(),
4662
- });
4663
- // Clear subscriptions through subscription service
4664
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").clearAll();
4665
- // Stop every chase loop: each holds a pending timer, and re-pricing after
4666
- // a disconnect would sign orders against a client that is being torn down.
4667
- // Orders already resting are deliberately left alone — disconnecting is
4668
- // not a request to cancel the user's positions or orders.
4669
- // Invalidate placements first, then drain every mutation before clearing
4670
- // the registry and tearing down the clients they use.
4671
- for (const [sessionId, session] of __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").entries()) {
4672
- if (session.active) {
4673
- __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_stopChaseSession).call(this, sessionId);
4674
- }
4675
- }
4676
- await Promise.all([...__classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementWaiters, "f")]);
4677
- await __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f");
4678
- await Promise.allSettled([...__classPrivateFieldGet(this, _HyperLiquidProvider_chaseTerminations, "f").values()]);
4679
- __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").clear();
4680
- __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTerminations, "f").clear();
4681
- __classPrivateFieldGet(this, _HyperLiquidProvider_terminatedChaseHandles, "f").clear();
4682
- __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").clear();
4683
- // Clear fee cache
4684
- this.clearFeeCache();
4685
- // Clear session caches (ensures fresh state on reconnect/account switch)
4686
- __classPrivateFieldGet(this, _HyperLiquidProvider_referralCheckCache, "f").clear();
4687
- __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").clear();
4688
- __classPrivateFieldSet(this, _HyperLiquidProvider_subscriptionBuilderApprovalEpoch, __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionBuilderApprovalEpoch, "f") + 1, "f");
4689
- __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").clear();
4690
- __classPrivateFieldSet(this, _HyperLiquidProvider_userFeeResolution, undefined, "f");
4691
- __classPrivateFieldSet(this, _HyperLiquidProvider_userFeeDiscountBips, undefined, "f");
4692
- // NOTE: UnifiedAccountCache is global and NOT cleared on disconnect
4693
- // to prevent repeated signing requests across reconnections
4694
- __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").clear();
4695
- __classPrivateFieldSet(this, _HyperLiquidProvider_cachedSpotMeta, null, "f");
4696
- __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").reset();
4697
- __classPrivateFieldSet(this, _HyperLiquidProvider_dexDiscoveryComplete, false, "f");
4698
- // Await pending initialization before clearing to prevent the IIFE from
4699
- // setting clientsInitialized = true after disconnect completes
4700
- const pendingInit = __classPrivateFieldGet(this, _HyperLiquidProvider_initializationPromise, "f");
4701
- const pendingReady = __classPrivateFieldGet(this, _HyperLiquidProvider_ensureReadyPromise, "f");
4702
- const pendingTradingSetup = __classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f");
4703
- // Clear references first to prevent new callers from reusing
4704
- __classPrivateFieldSet(this, _HyperLiquidProvider_initializationPromise, null, "f");
4705
- __classPrivateFieldSet(this, _HyperLiquidProvider_ensureReadyPromise, null, "f");
4706
- __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, null, "f");
4707
- __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupComplete, false, "f");
4708
- __classPrivateFieldGet(this, _HyperLiquidProvider_pendingBuilderFeeApprovals, "f").clear();
4709
- // Wait for pending operations to complete (ignore errors)
4710
- // This prevents IIFEs from setting state after disconnect completes
4711
- if (pendingInit) {
4712
- try {
4713
- await pendingInit;
4714
- }
4715
- catch {
4716
- // Ignore - we're disconnecting anyway
4717
- }
4718
- }
4719
- if (pendingReady) {
4720
- try {
4721
- await pendingReady;
4722
- }
4723
- catch {
4724
- // Ignore - we're disconnecting anyway
4725
- }
4726
- }
4727
- if (pendingTradingSetup) {
4728
- try {
4729
- await pendingTradingSetup;
4730
- }
4731
- catch {
4732
- // Ignore - we're disconnecting anyway
4733
- }
4734
- }
4735
- // Reset client initialization flag so wallet adapter will be recreated with new account
4736
- // This fixes account synchronization issue where old account's address persists in wallet adapter
4737
- __classPrivateFieldSet(this, _HyperLiquidProvider_clientsInitialized, false, "f");
4738
- // Disconnect client service
4739
- await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").disconnect();
4740
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Provider fully disconnected', {
4741
- timestamp: new Date().toISOString(),
4742
- });
4743
- return { success: true };
4744
- }
4745
- catch (error) {
4746
- return createErrorResult(error, { success: false });
5281
+ return await operation;
4747
5282
  }
4748
5283
  finally {
4749
- __classPrivateFieldSet(this, _HyperLiquidProvider_chasePlacementBlockers, __classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") - 1, "f");
5284
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationPromise, "f") === operation) {
5285
+ __classPrivateFieldSet(this, _HyperLiquidProvider_disconnectOperationPromise, null, "f");
5286
+ }
4750
5287
  }
4751
5288
  }
4752
5289
  /**
@@ -4872,7 +5409,75 @@ export class HyperLiquidProvider {
4872
5409
  return `${baseUrl}/explorer`;
4873
5410
  }
4874
5411
  }
4875
- _HyperLiquidProvider_deps = new WeakMap(), _HyperLiquidProvider_clientService = new WeakMap(), _HyperLiquidProvider_walletService = new WeakMap(), _HyperLiquidProvider_subscriptionService = new WeakMap(), _HyperLiquidProvider_symbolToAssetId = new WeakMap(), _HyperLiquidProvider_userFeeCache = new WeakMap(), _HyperLiquidProvider_maxLeverageCache = new WeakMap(), _HyperLiquidProvider_cachedMetaByDex = new WeakMap(), _HyperLiquidProvider_cachedMarketDataWithPrices = new WeakMap(), _HyperLiquidProvider_cachedSpotMeta = new WeakMap(), _HyperLiquidProvider_dexDiscoveryCache = new WeakMap(), _HyperLiquidProvider_referralCheckCache = new WeakMap(), _HyperLiquidProvider_builderFeeCheckCache = new WeakMap(), _HyperLiquidProvider_ensureReadyPromise = new WeakMap(), _HyperLiquidProvider_pendingBuilderFeeApprovals = new WeakMap(), _HyperLiquidProvider_subscriptionBuilderApprovalEpoch = new WeakMap(), _HyperLiquidProvider_approvedBuilderAddresses = new WeakMap(), _HyperLiquidProvider_compiledAllowlistPatterns = new WeakMap(), _HyperLiquidProvider_compiledBlocklistPatterns = new WeakMap(), _HyperLiquidProvider_userFeeDiscountBips = new WeakMap(), _HyperLiquidProvider_userFeeResolution = new WeakMap(), _HyperLiquidProvider_hip3Enabled = new WeakMap(), _HyperLiquidProvider_allowlistMarkets = new WeakMap(), _HyperLiquidProvider_blocklistMarkets = new WeakMap(), _HyperLiquidProvider_useUnifiedAccount = new WeakMap(), _HyperLiquidProvider_dexDiscoveryComplete = new WeakMap(), _HyperLiquidProvider_unifiedAccountSetupNeedsRetry = new WeakMap(), _HyperLiquidProvider_pendingValidatedDexsPromise = new WeakMap(), _HyperLiquidProvider_cachedUsdcTokenId = new WeakMap(), _HyperLiquidProvider_errorMappings = new WeakMap(), _HyperLiquidProvider_scaleOrderGroups = new WeakMap(), _HyperLiquidProvider_chaseSessions = new WeakMap(), _HyperLiquidProvider_chaseTerminations = new WeakMap(), _HyperLiquidProvider_terminatedChaseHandles = new WeakMap(), _HyperLiquidProvider_chasePlacementsInFlight = new WeakMap(), _HyperLiquidProvider_chasePlacementWaiters = new WeakMap(), _HyperLiquidProvider_chaseTickQueue = new WeakMap(), _HyperLiquidProvider_chaseGeneration = new WeakMap(), _HyperLiquidProvider_chasePlacementBlockers = new WeakMap(), _HyperLiquidProvider_clientsInitialized = new WeakMap(), _HyperLiquidProvider_initializationPromise = new WeakMap(), _HyperLiquidProvider_messenger = new WeakMap(), _HyperLiquidProvider_builderAddressTestnet = new WeakMap(), _HyperLiquidProvider_builderAddressMainnet = new WeakMap(), _HyperLiquidProvider_subscriptionBuilderAddressTestnet = new WeakMap(), _HyperLiquidProvider_subscriptionBuilderAddressMainnet = new WeakMap(), _HyperLiquidProvider_priceDeviationLimit = new WeakMap(), _HyperLiquidProvider_tradingSetupPromise = new WeakMap(), _HyperLiquidProvider_tradingSetupComplete = new WeakMap(), _HyperLiquidProvider_instances = new WeakSet(), _HyperLiquidProvider_compilePatternsSafely = function _HyperLiquidProvider_compilePatternsSafely(patterns, listName) {
5412
+ _a = HyperLiquidProvider, _HyperLiquidProvider_deps = new WeakMap(), _HyperLiquidProvider_clientService = new WeakMap(), _HyperLiquidProvider_walletService = new WeakMap(), _HyperLiquidProvider_subscriptionService = new WeakMap(), _HyperLiquidProvider_symbolToAssetId = new WeakMap(), _HyperLiquidProvider_userFeeCache = new WeakMap(), _HyperLiquidProvider_maxLeverageCache = new WeakMap(), _HyperLiquidProvider_cachedMetaByDex = new WeakMap(), _HyperLiquidProvider_orderCapabilitiesMarketsByDex = new WeakMap(), _HyperLiquidProvider_orderCapabilitiesRefreshByDex = new WeakMap(), _HyperLiquidProvider_isDisconnected = new WeakMap(), _HyperLiquidProvider_disconnectOperationsInFlight = new WeakMap(), _HyperLiquidProvider_disconnectOperationPromise = new WeakMap(), _HyperLiquidProvider_lifecycleGeneration = new WeakMap(), _HyperLiquidProvider_cachedMarketDataWithPrices = new WeakMap(), _HyperLiquidProvider_cachedSpotMeta = new WeakMap(), _HyperLiquidProvider_dexDiscoveryCache = new WeakMap(), _HyperLiquidProvider_referralCheckCache = new WeakMap(), _HyperLiquidProvider_builderFeeCheckCache = new WeakMap(), _HyperLiquidProvider_ensureReadyPromise = new WeakMap(), _HyperLiquidProvider_pendingBuilderFeeApprovals = new WeakMap(), _HyperLiquidProvider_subscriptionBuilderApprovalEpoch = new WeakMap(), _HyperLiquidProvider_approvedBuilderAddresses = new WeakMap(), _HyperLiquidProvider_compiledAllowlistPatterns = new WeakMap(), _HyperLiquidProvider_compiledBlocklistPatterns = new WeakMap(), _HyperLiquidProvider_userFeeDiscountBips = new WeakMap(), _HyperLiquidProvider_userFeeResolution = new WeakMap(), _HyperLiquidProvider_hip3Enabled = new WeakMap(), _HyperLiquidProvider_allowlistMarkets = new WeakMap(), _HyperLiquidProvider_blocklistMarkets = new WeakMap(), _HyperLiquidProvider_useUnifiedAccount = new WeakMap(), _HyperLiquidProvider_dexDiscoveryComplete = new WeakMap(), _HyperLiquidProvider_unifiedAccountSetupNeedsRetry = new WeakMap(), _HyperLiquidProvider_pendingValidatedDexsPromise = new WeakMap(), _HyperLiquidProvider_cachedUsdcTokenId = new WeakMap(), _HyperLiquidProvider_errorMappings = new WeakMap(), _HyperLiquidProvider_scaleOrderGroups = new WeakMap(), _HyperLiquidProvider_cancelledScaleOrderGroups = new WeakMap(), _HyperLiquidProvider_trackedTwapOrders = new WeakMap(), _HyperLiquidProvider_chaseSessions = new WeakMap(), _HyperLiquidProvider_chaseTerminations = new WeakMap(), _HyperLiquidProvider_terminatedChaseHandles = new WeakMap(), _HyperLiquidProvider_chasePlacementsInFlight = new WeakMap(), _HyperLiquidProvider_chasePlacementWaiters = new WeakMap(), _HyperLiquidProvider_chaseTickQueue = new WeakMap(), _HyperLiquidProvider_strategyGeneration = new WeakMap(), _HyperLiquidProvider_chasePlacementBlockers = new WeakMap(), _HyperLiquidProvider_clientsInitialized = new WeakMap(), _HyperLiquidProvider_initializationPromise = new WeakMap(), _HyperLiquidProvider_messenger = new WeakMap(), _HyperLiquidProvider_builderAddressTestnet = new WeakMap(), _HyperLiquidProvider_builderAddressMainnet = new WeakMap(), _HyperLiquidProvider_subscriptionBuilderAddressTestnet = new WeakMap(), _HyperLiquidProvider_subscriptionBuilderAddressMainnet = new WeakMap(), _HyperLiquidProvider_priceDeviationLimit = new WeakMap(), _HyperLiquidProvider_onChaseOrderMaxDistanceReached = new WeakMap(), _HyperLiquidProvider_tradingSetupPromise = new WeakMap(), _HyperLiquidProvider_tradingSetupComplete = new WeakMap(), _HyperLiquidProvider_builderFeeSetupPromises = new WeakMap(), _HyperLiquidProvider_instances = new WeakSet(), _HyperLiquidProvider_resolveOrderFeePolicy = function _HyperLiquidProvider_resolveOrderFeePolicy(params) {
5413
+ const fallbackPolicy = Object.prototype.hasOwnProperty.call(HYPERLIQUID_ORDER_FEE_CONFIG, params.orderType)
5414
+ ? HYPERLIQUID_ORDER_FEE_CONFIG[params.orderType]
5415
+ : undefined;
5416
+ if (fallbackPolicy) {
5417
+ return fallbackPolicy;
5418
+ }
5419
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Unknown order type used the safe builder-fee policy', { orderType: params.orderType });
5420
+ return HYPERLIQUID_ORDER_FEE_CONFIG.market;
5421
+ }, _HyperLiquidProvider_assertProviderLifecycleCurrent = function _HyperLiquidProvider_assertProviderLifecycleCurrent(generation, operation) {
5422
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_isDisconnected, "f") || generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f")) {
5423
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Provider operation became stale during disconnect', { operation });
5424
+ throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5425
+ }
5426
+ }, _HyperLiquidProvider_assertCacheWriteLifecycleCurrent = function _HyperLiquidProvider_assertCacheWriteLifecycleCurrent(generation, operation) {
5427
+ if (!__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, generation, operation)) {
5428
+ throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5429
+ }
5430
+ }, _HyperLiquidProvider_isCacheWriteLifecycleCurrent = function _HyperLiquidProvider_isCacheWriteLifecycleCurrent(generation, operation) {
5431
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0 ||
5432
+ generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f")) {
5433
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Cache write became stale during disconnect', { operation });
5434
+ return false;
5435
+ }
5436
+ return true;
5437
+ }, _HyperLiquidProvider_getFreshOrderCapabilityMarkets =
5438
+ /**
5439
+ * Return filtered market metadata fresh enough for a capability query.
5440
+ * Each DEX shares one refresh across concurrent callers, and a failed
5441
+ * refresh remains immediately retryable.
5442
+ *
5443
+ * @param dex - Routed DEX, or null for the main DEX.
5444
+ * @returns Fresh filtered markets for that route.
5445
+ */
5446
+ async function _HyperLiquidProvider_getFreshOrderCapabilityMarkets(dex) {
5447
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5448
+ const cachedMarkets = __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesMarketsByDex, "f").get(dex);
5449
+ if (cachedMarkets &&
5450
+ Date.now() - cachedMarkets.freshAt <
5451
+ PERFORMANCE_CONFIG.OrderCapabilitiesMetaFreshnessMs) {
5452
+ return cachedMarkets.markets;
5453
+ }
5454
+ let refreshPromise = __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesRefreshByDex, "f").get(dex);
5455
+ if (!refreshPromise) {
5456
+ refreshPromise = (async () => {
5457
+ const markets = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchMarketsForDex).call(this, {
5458
+ dex,
5459
+ skipCache: true,
5460
+ });
5461
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Order capability metadata refresh');
5462
+ __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesMarketsByDex, "f").set(dex, {
5463
+ markets,
5464
+ freshAt: Date.now(),
5465
+ });
5466
+ return markets;
5467
+ })();
5468
+ __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesRefreshByDex, "f").set(dex, refreshPromise);
5469
+ }
5470
+ try {
5471
+ const markets = await refreshPromise;
5472
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Order capability read');
5473
+ return markets;
5474
+ }
5475
+ finally {
5476
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesRefreshByDex, "f").get(dex) === refreshPromise) {
5477
+ __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesRefreshByDex, "f").delete(dex);
5478
+ }
5479
+ }
5480
+ }, _HyperLiquidProvider_compilePatternsSafely = function _HyperLiquidProvider_compilePatternsSafely(patterns, listName) {
4876
5481
  const compiled = [];
4877
5482
  for (const pattern of patterns) {
4878
5483
  try {
@@ -4895,6 +5500,9 @@ _HyperLiquidProvider_deps = new WeakMap(), _HyperLiquidProvider_clientService =
4895
5500
  * the connection is fully established before marking initialization complete.
4896
5501
  */
4897
5502
  async function _HyperLiquidProvider_ensureClientsInitialized() {
5503
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0) {
5504
+ throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5505
+ }
4898
5506
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_clientsInitialized, "f")) {
4899
5507
  return; // Already initialized
4900
5508
  }
@@ -4902,6 +5510,9 @@ async function _HyperLiquidProvider_ensureClientsInitialized() {
4902
5510
  // This prevents race conditions when multiple methods call concurrently
4903
5511
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_initializationPromise, "f")) {
4904
5512
  await __classPrivateFieldGet(this, _HyperLiquidProvider_initializationPromise, "f");
5513
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0) {
5514
+ throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5515
+ }
4905
5516
  return;
4906
5517
  }
4907
5518
  // Create and cache the initialization promise
@@ -4910,8 +5521,14 @@ async function _HyperLiquidProvider_ensureClientsInitialized() {
4910
5521
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_clientsInitialized, "f")) {
4911
5522
  return;
4912
5523
  }
5524
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0) {
5525
+ throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5526
+ }
4913
5527
  const wallet = __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").createWalletAdapter();
4914
5528
  await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").initialize(wallet);
5529
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0) {
5530
+ throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5531
+ }
4915
5532
  // Set termination callback for logging when WebSocket terminates
4916
5533
  // Note: Do NOT restore subscriptions here - termination means connection failed permanently
4917
5534
  __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").setOnTerminateCallback((error) => {
@@ -5382,7 +5999,13 @@ async function _HyperLiquidProvider_ensureReady() {
5382
5999
  // Await initialization - keep the promise so subsequent calls resolve immediately
5383
6000
  // The promise is only reset in disconnect() for clean reconnection,
5384
6001
  // or when DEX discovery was degraded so the next caller retries.
5385
- await __classPrivateFieldGet(this, _HyperLiquidProvider_ensureReadyPromise, "f");
6002
+ try {
6003
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_ensureReadyPromise, "f");
6004
+ }
6005
+ catch (error) {
6006
+ __classPrivateFieldSet(this, _HyperLiquidProvider_ensureReadyPromise, null, "f");
6007
+ throw error;
6008
+ }
5386
6009
  if (!__classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryComplete, "f")) {
5387
6010
  // DEX discovery failed transiently — reset so next call retries.
5388
6011
  // Trading still works (main DEX mapping is populated), but HIP-3 markets
@@ -5397,72 +6020,102 @@ async function _HyperLiquidProvider_ensureReady() {
5397
6020
  __classPrivateFieldSet(this, _HyperLiquidProvider_ensureReadyPromise, null, "f");
5398
6021
  }
5399
6022
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReady] Initialization complete');
5400
- }, _HyperLiquidProvider_ensureReadyForTrading = async function _HyperLiquidProvider_ensureReadyForTrading() {
6023
+ }, _HyperLiquidProvider_ensureBuilderFeeSetup =
6024
+ /**
6025
+ * Approve the standard builder fee once for the current account and network.
6026
+ * TWAP never calls this because its native action has no builder field.
6027
+ * Without an approval failure code, approval remains non-blocking and the
6028
+ * returned context describes attribution only; it does not prove approval.
6029
+ *
6030
+ * @param approvalFailureCode - Operation-specific error to throw when
6031
+ * approval is unavailable or fails.
6032
+ * @returns The account, network, and configured builder for the action.
6033
+ */
6034
+ async function _HyperLiquidProvider_ensureBuilderFeeSetup(approvalFailureCode) {
6035
+ const isTestnet = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode();
6036
+ const network = isTestnet ? 'testnet' : 'mainnet';
6037
+ const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
6038
+ const cacheKey = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCacheKey).call(this, network, userAddress);
6039
+ const builderAddress = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderAddress).call(this, isTestnet);
6040
+ const context = {
6041
+ network,
6042
+ userAddress,
6043
+ builderAddress,
6044
+ };
6045
+ const setupKey = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getApprovedBuilderKey).call(this, network, userAddress, builderAddress);
6046
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").has(cacheKey)) {
6047
+ return context;
6048
+ }
6049
+ let pendingApproval = __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").get(setupKey);
6050
+ if (!pendingApproval) {
6051
+ pendingApproval = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureBuilderFeeApproval).call(this, context);
6052
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").set(setupKey, pendingApproval);
6053
+ }
6054
+ try {
6055
+ await pendingApproval;
6056
+ }
6057
+ catch (error) {
6058
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureBuilderFeeSetup] Builder fee approval failed', error);
6059
+ if (approvalFailureCode) {
6060
+ throw new Error(approvalFailureCode);
6061
+ }
6062
+ }
6063
+ finally {
6064
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").get(setupKey) === pendingApproval) {
6065
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").delete(setupKey);
6066
+ }
6067
+ }
6068
+ if (approvalFailureCode && !__classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").has(cacheKey)) {
6069
+ throw new Error(approvalFailureCode);
6070
+ }
6071
+ return context;
6072
+ }, _HyperLiquidProvider_ensureReadyForTrading = async function _HyperLiquidProvider_ensureReadyForTrading(options) {
5401
6073
  // First ensure basic initialization is complete
5402
6074
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReady).call(this);
5403
6075
  // dexAbstraction users were deferred during init to avoid an EIP-712 prompt
5404
6076
  // on Perps section open. Drive the migration here, gated by its own cache so
5405
6077
  // already-migrated or already-rejected users are not re-prompted.
5406
6078
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureUnifiedAccountEnabled).call(this, { allowUserSigning: true });
5407
- // If trading setup already complete, only retry builder fee if it previously failed
5408
- if (__classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupComplete, "f")) {
5409
- const isTestnet = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode();
5410
- const network = isTestnet ? 'testnet' : 'mainnet';
5411
- const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
5412
- const cacheKey = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCacheKey).call(this, network, userAddress);
5413
- if (!__classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").has(cacheKey)) {
5414
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Retrying builder fee approval (previous attempt failed)');
5415
- try {
5416
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureBuilderFeeApproval).call(this);
6079
+ if (!__classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupComplete, "f") && !__classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f")) {
6080
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
6081
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Starting shared trading setup');
6082
+ __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, (async () => {
6083
+ // Pre-fetch spotMeta for HIP-3 operations (non-blocking if it fails)
6084
+ // This ensures token info (e.g. USDC token index) is available during order placement
6085
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_hip3Enabled, "f")) {
6086
+ try {
6087
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCachedSpotMeta).call(this);
6088
+ }
6089
+ catch (error) {
6090
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] spotMeta pre-fetch failed, will retry when needed', error);
6091
+ // Don't throw - spotMeta will be fetched on-demand if needed
6092
+ }
5417
6093
  }
5418
- catch (error) {
5419
- // Don't throw - retry is best-effort, trading continues regardless
5420
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Builder fee retry failed', error);
6094
+ // Set up referral code independently from builder-fee applicability.
6095
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReferralSet).call(this);
6096
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Trading setup completion');
6097
+ // Only mark complete if keyring was unlocked (signing could actually happen)
6098
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").isKeyringUnlocked()) {
6099
+ __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupComplete, true, "f");
5421
6100
  }
5422
- }
5423
- return;
5424
- }
5425
- // If trading setup is in progress, wait for it
5426
- if (__classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f")) {
5427
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Waiting for in-progress trading setup');
5428
- await __classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f");
5429
- return;
6101
+ })(), "f");
5430
6102
  }
5431
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Starting trading setup (may require signatures)');
5432
- __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, (async () => {
5433
- // Pre-fetch spotMeta for HIP-3 operations (non-blocking if it fails)
5434
- // This ensures token info (e.g. USDC token index) is available during order placement
5435
- if (__classPrivateFieldGet(this, _HyperLiquidProvider_hip3Enabled, "f")) {
5436
- try {
5437
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCachedSpotMeta).call(this);
5438
- }
5439
- catch (error) {
5440
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] spotMeta pre-fetch failed, will retry when needed', error);
5441
- // Don't throw - spotMeta will be fetched on-demand if needed
5442
- }
5443
- }
5444
- // Set up builder fee approval
6103
+ const pendingSetup = __classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f");
6104
+ if (pendingSetup) {
5445
6105
  try {
5446
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureBuilderFeeApproval).call(this);
5447
- }
5448
- catch (error) {
5449
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquidProvider: Builder fee approval failed', error);
5450
- // Don't throw - let trading continue, will fail with clear error if needed
6106
+ await pendingSetup;
5451
6107
  }
5452
- // Set up referral code
5453
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReferralSet).call(this);
5454
- // Only mark complete if keyring was unlocked (signing could actually happen)
5455
- if (__classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").isKeyringUnlocked()) {
5456
- __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupComplete, true, "f");
6108
+ finally {
6109
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f") === pendingSetup) {
6110
+ __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, null, "f");
6111
+ }
5457
6112
  }
5458
- })(), "f");
5459
- try {
5460
- await __classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f");
5461
- }
5462
- finally {
5463
- __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, null, "f");
5464
6113
  }
6114
+ const builderFeeSetupContext = options.requiresBuilderFee
6115
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureBuilderFeeSetup).call(this, options.builderFeeApprovalFailureCode)
6116
+ : undefined;
5465
6117
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Trading setup complete');
6118
+ return builderFeeSetupContext;
5466
6119
  }, _HyperLiquidProvider_getOrFetchPrice =
5467
6120
  /**
5468
6121
  * Get current price for a symbol using WebSocket cache first, REST API fallback
@@ -5525,6 +6178,7 @@ async function _HyperLiquidProvider_getOrFetchPrice(params) {
5525
6178
  * @returns Array of all DEX names (null for main DEX, strings for HIP-3 DEXs)
5526
6179
  */
5527
6180
  async function _HyperLiquidProvider_getAllAvailableDexs() {
6181
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5528
6182
  // Use unified state if available
5529
6183
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").state) {
5530
6184
  const availableHip3Dexs = [];
@@ -5542,6 +6196,7 @@ async function _HyperLiquidProvider_getAllAvailableDexs() {
5542
6196
  if (!allDexs || !Array.isArray(allDexs)) {
5543
6197
  return [null]; // Fallback to main DEX only
5544
6198
  }
6199
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'DEX discovery cache write');
5545
6200
  const state = __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").update(allDexs);
5546
6201
  const availableHip3Dexs = [];
5547
6202
  state.raw.forEach((dex) => {
@@ -5588,14 +6243,17 @@ async function _HyperLiquidProvider_getValidatedDexs() {
5588
6243
  return __classPrivateFieldGet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, "f");
5589
6244
  }
5590
6245
  // Create and cache the pending promise for deduplication
5591
- __classPrivateFieldSet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchValidatedDexsInternal).call(this), "f");
6246
+ const pendingPromise = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchValidatedDexsInternal).call(this);
6247
+ __classPrivateFieldSet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, pendingPromise, "f");
5592
6248
  try {
5593
- const result = await __classPrivateFieldGet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, "f");
6249
+ const result = await pendingPromise;
5594
6250
  return result;
5595
6251
  }
5596
6252
  finally {
5597
- // Clear the pending promise when done (success or error)
5598
- __classPrivateFieldSet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, null, "f");
6253
+ // A stale request must not clear a newer lifecycle's pending request.
6254
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, "f") === pendingPromise) {
6255
+ __classPrivateFieldSet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, null, "f");
6256
+ }
5599
6257
  }
5600
6258
  }, _HyperLiquidProvider_fetchValidatedDexsInternal =
5601
6259
  /**
@@ -5605,6 +6263,7 @@ async function _HyperLiquidProvider_getValidatedDexs() {
5605
6263
  * @returns A promise that resolves to the result.
5606
6264
  */
5607
6265
  async function _HyperLiquidProvider_fetchValidatedDexsInternal() {
6266
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5608
6267
  // Kill switch: HIP-3 disabled, return main DEX only
5609
6268
  if (!__classPrivateFieldGet(this, _HyperLiquidProvider_hip3Enabled, "f")) {
5610
6269
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquidProvider: HIP-3 disabled via hip3Enabled flag');
@@ -5634,6 +6293,7 @@ async function _HyperLiquidProvider_fetchValidatedDexsInternal() {
5634
6293
  return [null];
5635
6294
  }
5636
6295
  // Atomically update unified state (raw + validated + timestamp)
6296
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'DEX discovery cache write');
5637
6297
  const state = __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").update(allDexs);
5638
6298
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquidProvider: Available DEXs (market filtering applied at data layer)', {
5639
6299
  count: state.validated.filter((dex) => dex !== null).length,
@@ -5652,13 +6312,12 @@ async function _HyperLiquidProvider_fetchValidatedDexsInternal() {
5652
6312
  * @throws Error if API returns invalid data
5653
6313
  */
5654
6314
  async function _HyperLiquidProvider_getCachedMeta(params) {
6315
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5655
6316
  const { dexName, skipCache } = params;
5656
- // Use empty string for main DEX key (consistent with buildAssetMapping cache population)
5657
- const dexKey = dexName ?? '';
5658
- const dexDisplayName = dexKey || 'main';
6317
+ const dexDisplayName = dexName ?? 'main';
5659
6318
  // Skip cache if requested (forces fresh fetch)
5660
6319
  if (!skipCache) {
5661
- const cached = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dexKey);
6320
+ const cached = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dexName);
5662
6321
  if (cached) {
5663
6322
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedMeta] Using cached meta response', {
5664
6323
  dex: dexDisplayName,
@@ -5668,6 +6327,29 @@ async function _HyperLiquidProvider_getCachedMeta(params) {
5668
6327
  }
5669
6328
  }
5670
6329
  // Cache miss or skipCache=true - fetch from API.
6330
+ const meta = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchMeta).call(this, dexName);
6331
+ const cached = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheDexMetadataFromRead).call(this, {
6332
+ dex: dexName,
6333
+ meta,
6334
+ lifecycleGeneration,
6335
+ });
6336
+ if (cached) {
6337
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedMeta] Fetched and cached meta response', {
6338
+ dex: dexDisplayName,
6339
+ universeSize: meta.universe.length,
6340
+ skipCache,
6341
+ });
6342
+ }
6343
+ return meta;
6344
+ }, _HyperLiquidProvider_fetchMeta =
6345
+ /**
6346
+ * Fetch and validate metadata without mutating provider caches.
6347
+ *
6348
+ * @param dexName - DEX name, or null for the main DEX.
6349
+ * @returns Validated metadata for the requested DEX.
6350
+ */
6351
+ async function _HyperLiquidProvider_fetchMeta(dexName) {
6352
+ const dexDisplayName = dexName ?? 'main';
5671
6353
  // Bring the SDK clients up first. This is the first client touch on the
5672
6354
  // write path — placeOrder resolves asset info before it ensures trading
5673
6355
  // readiness — so without it a cold start or a post-disconnect action fails
@@ -5679,20 +6361,54 @@ async function _HyperLiquidProvider_getCachedMeta(params) {
5679
6361
  const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient({ useHttp: true });
5680
6362
  // Pass dex only for HIP-3 DEXs; omit for main DEX (empty string).
5681
6363
  // Testnet API returns null when dex="" is explicitly sent.
5682
- const meta = await infoClient.meta(dexKey ? { dex: dexKey } : undefined);
6364
+ const meta = await infoClient.meta(dexName ? { dex: dexName } : undefined);
5683
6365
  // Defensive validation before caching
5684
6366
  if (!meta?.universe || !Array.isArray(meta.universe)) {
5685
6367
  throw new Error(`[HyperLiquidProvider] Invalid meta response for DEX ${dexDisplayName}: universe is ${meta?.universe ? 'not an array' : 'missing'}`);
5686
6368
  }
5687
- // Store raw meta response for reuse
5688
- __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").set(dexKey, meta);
5689
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dexName, meta);
5690
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedMeta] Fetched and cached meta response', {
5691
- dex: dexDisplayName,
5692
- universeSize: meta.universe.length,
5693
- skipCache,
5694
- });
5695
6369
  return meta;
6370
+ }, _HyperLiquidProvider_cacheDexMetadata = function _HyperLiquidProvider_cacheDexMetadata(params) {
6371
+ const { dex, meta, assetCtxs, lifecycleGeneration } = params;
6372
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'DEX metadata cache write');
6373
+ __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").set(dex, meta);
6374
+ if (assetCtxs) {
6375
+ const subscriptionDexKey = dex ?? '';
6376
+ __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexMetaCache(subscriptionDexKey, meta);
6377
+ __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexAssetCtxsCache(subscriptionDexKey, assetCtxs);
6378
+ }
6379
+ }, _HyperLiquidProvider_cacheDexMetadataFromRead =
6380
+ /**
6381
+ * Cache metadata fetched for a read without discarding a valid response.
6382
+ *
6383
+ * @param params - Metadata and lifecycle context.
6384
+ * @param params.dex - DEX name, or null for the main DEX.
6385
+ * @param params.meta - Validated DEX metadata.
6386
+ * @param params.assetCtxs - Asset contexts shared with subscriptions, if any.
6387
+ * @param params.lifecycleGeneration - Lifecycle that produced the metadata.
6388
+ * @returns Whether the fetched metadata was cached.
6389
+ */
6390
+ async function _HyperLiquidProvider_cacheDexMetadataFromRead(params) {
6391
+ const { dex, meta, assetCtxs, lifecycleGeneration } = params;
6392
+ if (!__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'DEX metadata read cache write')) {
6393
+ return false;
6394
+ }
6395
+ try {
6396
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheDexMetadata).call(this, {
6397
+ dex,
6398
+ meta,
6399
+ assetCtxs,
6400
+ lifecycleGeneration,
6401
+ });
6402
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dex, meta, lifecycleGeneration);
6403
+ return true;
6404
+ }
6405
+ catch (error) {
6406
+ if (ensureError(error, 'HyperLiquidProvider.cacheDexMetadataFromRead')
6407
+ .message === PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE) {
6408
+ return false;
6409
+ }
6410
+ throw error;
6411
+ }
5696
6412
  }, _HyperLiquidProvider_backfillAssetMapForDex =
5697
6413
  /**
5698
6414
  * Backfill the asset ID map for a single DEX from a fresh meta response.
@@ -5700,9 +6416,11 @@ async function _HyperLiquidProvider_getCachedMeta(params) {
5700
6416
  *
5701
6417
  * @param dex - DEX name (null for main DEX).
5702
6418
  * @param meta - Meta response containing the DEX universe.
6419
+ * @param lifecycleGeneration - Lifecycle that produced the metadata.
5703
6420
  * @returns True if the mapping was rebuilt for the DEX.
5704
6421
  */
5705
- async function _HyperLiquidProvider_backfillAssetMapForDex(dex, meta) {
6422
+ async function _HyperLiquidProvider_backfillAssetMapForDex(dex, meta, lifecycleGeneration) {
6423
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Asset map backfill');
5706
6424
  if (!meta?.universe || !Array.isArray(meta.universe)) {
5707
6425
  return false;
5708
6426
  }
@@ -5717,6 +6435,7 @@ async function _HyperLiquidProvider_backfillAssetMapForDex(dex, meta) {
5717
6435
  });
5718
6436
  }
5719
6437
  }
6438
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Asset map backfill');
5720
6439
  const allPerpDexs = __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").state?.raw ?? [null];
5721
6440
  const perpDexIndex = allPerpDexs.findIndex((entry) => {
5722
6441
  if (dex === null) {
@@ -5753,6 +6472,7 @@ async function _HyperLiquidProvider_backfillAssetMapForDex(dex, meta) {
5753
6472
  * @returns The asset ID.
5754
6473
  */
5755
6474
  async function _HyperLiquidProvider_getAssetIdWithRepair(params) {
6475
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5756
6476
  const { symbol, dexName } = params;
5757
6477
  const existingAssetId = __classPrivateFieldGet(this, _HyperLiquidProvider_symbolToAssetId, "f").get(symbol);
5758
6478
  if (existingAssetId !== undefined) {
@@ -5761,7 +6481,7 @@ async function _HyperLiquidProvider_getAssetIdWithRepair(params) {
5761
6481
  const meta = params.meta ?? (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCachedMeta).call(this, { dexName: dexName ?? null }));
5762
6482
  const assetExistsInMeta = meta.universe.some((asset) => asset.name === symbol);
5763
6483
  if (assetExistsInMeta) {
5764
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dexName, meta);
6484
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dexName, meta, lifecycleGeneration);
5765
6485
  const repairedAssetId = __classPrivateFieldGet(this, _HyperLiquidProvider_symbolToAssetId, "f").get(symbol);
5766
6486
  if (repairedAssetId !== undefined) {
5767
6487
  return repairedAssetId;
@@ -5792,13 +6512,16 @@ async function _HyperLiquidProvider_getCachedSpotMeta() {
5792
6512
  });
5793
6513
  return __classPrivateFieldGet(this, _HyperLiquidProvider_cachedSpotMeta, "f");
5794
6514
  }
6515
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5795
6516
  const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient();
5796
6517
  const spotMeta = await infoClient.spotMeta();
5797
- __classPrivateFieldSet(this, _HyperLiquidProvider_cachedSpotMeta, spotMeta, "f");
5798
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedSpotMeta] Fetched and cached spotMeta', {
5799
- tokensCount: spotMeta.tokens.length,
5800
- universeCount: spotMeta.universe.length,
5801
- });
6518
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Spot metadata cache write')) {
6519
+ __classPrivateFieldSet(this, _HyperLiquidProvider_cachedSpotMeta, spotMeta, "f");
6520
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedSpotMeta] Fetched and cached spotMeta', {
6521
+ tokensCount: spotMeta.tokens.length,
6522
+ universeCount: spotMeta.universe.length,
6523
+ });
6524
+ }
5802
6525
  return spotMeta;
5803
6526
  }, _HyperLiquidProvider_getCachedPerpDexs =
5804
6527
  /**
@@ -5808,6 +6531,7 @@ async function _HyperLiquidProvider_getCachedSpotMeta() {
5808
6531
  * @returns Array of ExtendedPerpDex objects (null entries represent main DEX)
5809
6532
  */
5810
6533
  async function _HyperLiquidProvider_getCachedPerpDexs() {
6534
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5811
6535
  const now = Date.now();
5812
6536
  // Return cached data if still valid (uses unified state timestamp for TTL)
5813
6537
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").state &&
@@ -5825,17 +6549,19 @@ async function _HyperLiquidProvider_getCachedPerpDexs() {
5825
6549
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureClientsInitialized).call(this);
5826
6550
  const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient();
5827
6551
  const perpDexs = (await infoClient.perpDexs());
5828
- // Atomically update unified state (raw + validated + timestamp)
5829
- __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").update(perpDexs);
5830
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedPerpDexs] Fetched and cached perpDexs data', {
5831
- count: perpDexs.length,
5832
- dexes: perpDexs
5833
- .filter((dex) => dex !== null)
5834
- .map((dex) => ({
5835
- name: dex.name,
5836
- deployerFeeScale: dex.deployerFeeScale,
5837
- })),
5838
- });
6552
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Perp DEX cache write')) {
6553
+ // Atomically update unified state (raw + validated + timestamp)
6554
+ __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").update(perpDexs);
6555
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedPerpDexs] Fetched and cached perpDexs data', {
6556
+ count: perpDexs.length,
6557
+ dexes: perpDexs
6558
+ .filter((dex) => dex !== null)
6559
+ .map((dex) => ({
6560
+ name: dex.name,
6561
+ deployerFeeScale: dex.deployerFeeScale,
6562
+ })),
6563
+ });
6564
+ }
5839
6565
  return perpDexs;
5840
6566
  }, _HyperLiquidProvider_calculateHip3FeeMultiplier =
5841
6567
  /**
@@ -6007,6 +6733,7 @@ async function _HyperLiquidProvider_isUsdcCollateralDex(dexName) {
6007
6733
  * the asset ID lookup succeeds and the order routes to the correct DEX.
6008
6734
  */
6009
6735
  async function _HyperLiquidProvider_buildAssetMapping() {
6736
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
6010
6737
  // Get feature-flag-validated DEXs to map (respects hip3Enabled and enabledDexs)
6011
6738
  let dexsToMap;
6012
6739
  try {
@@ -6037,14 +6764,14 @@ async function _HyperLiquidProvider_buildAssetMapping() {
6037
6764
  // Extract HIP-3 DEX names (filter out null which represents main DEX)
6038
6765
  const enabledDexs = dexsToMap.filter((dex) => dex !== null);
6039
6766
  await __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").updateFeatureFlags(__classPrivateFieldGet(this, _HyperLiquidProvider_hip3Enabled, "f"), enabledDexs, __classPrivateFieldGet(this, _HyperLiquidProvider_allowlistMarkets, "f"), __classPrivateFieldGet(this, _HyperLiquidProvider_blocklistMarkets, "f"));
6767
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Asset mapping rebuild');
6040
6768
  // Fetch metadata for each DEX in parallel using metaAndAssetCtxs
6041
6769
  // Optimization: Check cache first - getMarketDataWithPrices may have already fetched
6042
6770
  // If not cached, fetch via metaAndAssetCtxs and populate cache for other methods
6043
6771
  const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient();
6044
6772
  const allMetas = await Promise.allSettled(dexsToMap.map((dex) => {
6045
- const dexKey = dex ?? '';
6046
6773
  // Check if already cached (e.g., by getMarketDataWithPrices running in parallel)
6047
- const cachedMeta = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dexKey);
6774
+ const cachedMeta = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dex);
6048
6775
  if (cachedMeta) {
6049
6776
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`[buildAssetMapping] Using cached meta for ${dex ?? 'main'}`, { universeSize: cachedMeta.universe.length });
6050
6777
  return Promise.resolve({
@@ -6062,11 +6789,12 @@ async function _HyperLiquidProvider_buildAssetMapping() {
6062
6789
  const assetCtxs = result?.[1] || [];
6063
6790
  // Cache meta for later use by getCachedMeta
6064
6791
  if (meta?.universe) {
6065
- __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").set(dexKey, meta);
6066
- // Also populate subscription service cache to avoid redundant API calls
6067
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexMetaCache(dexKey, meta);
6068
- // Cache assetCtxs for getMarketDataWithPrices (avoids duplicate metaAndAssetCtxs calls)
6069
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexAssetCtxsCache(dexKey, assetCtxs);
6792
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheDexMetadata).call(this, {
6793
+ dex,
6794
+ meta,
6795
+ assetCtxs,
6796
+ lifecycleGeneration,
6797
+ });
6070
6798
  }
6071
6799
  return { dex, meta, success: true };
6072
6800
  })
@@ -6075,6 +6803,7 @@ async function _HyperLiquidProvider_buildAssetMapping() {
6075
6803
  return { dex, meta: null, success: false };
6076
6804
  });
6077
6805
  }));
6806
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Asset mapping rebuild');
6078
6807
  // Build mapping with DEX prefixes for HIP-3 DEXs using the utility function
6079
6808
  __classPrivateFieldGet(this, _HyperLiquidProvider_symbolToAssetId, "f").clear();
6080
6809
  let dexDiscoveryComplete = __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").state !== null;
@@ -6246,12 +6975,16 @@ async function _HyperLiquidProvider_checkBuilderFeeApproval(builder, userAddress
6246
6975
  * This prevents repeated signing requests for hardware wallets.
6247
6976
  *
6248
6977
  * Note: This is network-specific - testnet and mainnet have separate builder fee states
6978
+ *
6979
+ * @param params - Builder fee setup context.
6980
+ * @param params.network - HyperLiquid network for the approval.
6981
+ * @param params.userAddress - Account that owns the approval.
6982
+ * @param params.builderAddress - Builder address being approved.
6249
6983
  */
6250
- async function _HyperLiquidProvider_ensureBuilderFeeApproval() {
6251
- const isTestnet = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode();
6252
- const network = isTestnet ? 'testnet' : 'mainnet';
6253
- const builderAddress = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderAddress).call(this, isTestnet);
6254
- const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
6984
+ async function _HyperLiquidProvider_ensureBuilderFeeApproval(params) {
6985
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
6986
+ const { network, userAddress, builderAddress } = params;
6987
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Builder fee approval');
6255
6988
  const cacheKey = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCacheKey).call(this, network, userAddress);
6256
6989
  // Check GLOBAL cache first to avoid repeated signing requests across reconnections
6257
6990
  // This is CRITICAL for hardware wallets to prevent repeated signing prompts
@@ -6268,6 +7001,11 @@ async function _HyperLiquidProvider_ensureBuilderFeeApproval() {
6268
7001
  if (inFlightPromise) {
6269
7002
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureBuilderFeeApproval] Global in-flight, waiting...', { network });
6270
7003
  await inFlightPromise;
7004
+ const completedApproval = PerpsSigningCache.getBuilderFee(network, userAddress);
7005
+ if (completedApproval?.attempted && completedApproval.success) {
7006
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").set(cacheKey, true);
7007
+ __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").add(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getApprovedBuilderKey).call(this, network, userAddress, builderAddress));
7008
+ }
6271
7009
  return;
6272
7010
  }
6273
7011
  // Set global in-flight lock
@@ -6277,10 +7015,13 @@ async function _HyperLiquidProvider_ensureBuilderFeeApproval() {
6277
7015
  const recheckCache = PerpsSigningCache.getBuilderFee(network, userAddress);
6278
7016
  if (recheckCache?.attempted && recheckCache?.success) {
6279
7017
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureBuilderFeeApproval] Completed by another provider', { network });
7018
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").set(cacheKey, true);
7019
+ __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").add(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getApprovedBuilderKey).call(this, network, userAddress, builderAddress));
6280
7020
  completeInFlight();
6281
7021
  return;
6282
7022
  }
6283
7023
  const { isApproved, requiredDecimal } = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_checkBuilderFeeStatus).call(this, builderAddress, userAddress);
7024
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Builder fee approval');
6284
7025
  if (isApproved) {
6285
7026
  // User already has approval on-chain
6286
7027
  PerpsSigningCache.setBuilderFee(network, userAddress, {
@@ -6305,11 +7046,15 @@ async function _HyperLiquidProvider_ensureBuilderFeeApproval() {
6305
7046
  afterApprovalDecimal < requiredDecimal) {
6306
7047
  throw new Error('[HyperLiquidProvider] Builder fee approval verification failed');
6307
7048
  }
6308
- // Cache success in BOTH global and instance caches
7049
+ // The approval is already confirmed on-chain. Preserve that fact for
7050
+ // the next provider lifecycle even if this instance became stale while
7051
+ // the approval request was in flight.
6309
7052
  PerpsSigningCache.setBuilderFee(network, userAddress, {
6310
7053
  attempted: true,
6311
7054
  success: true,
6312
7055
  });
7056
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Builder fee approval');
7057
+ // Cache success in the current instance after the lifecycle check.
6313
7058
  __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").set(cacheKey, true);
6314
7059
  __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").add(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getApprovedBuilderKey).call(this, network, userAddress, builderAddress));
6315
7060
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureBuilderFeeApproval] Approval successful', {
@@ -6320,6 +7065,11 @@ async function _HyperLiquidProvider_ensureBuilderFeeApproval() {
6320
7065
  completeInFlight();
6321
7066
  }
6322
7067
  catch (error) {
7068
+ if (ensureError(error, 'HyperLiquidProvider.ensureBuilderFeeApproval')
7069
+ .message === PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE) {
7070
+ completeInFlight();
7071
+ return;
7072
+ }
6323
7073
  // HyperLiquid wraps wallet signing failures and preserves KEYRING_LOCKED
6324
7074
  // in `cause`, so classify the full chain and leave retry caches empty.
6325
7075
  if (isKeyringLockedError(error)) {
@@ -6657,6 +7407,7 @@ async function _HyperLiquidProvider_calculateHip3RequiredMargin(params) {
6657
7407
  * @param params.transferInfo - The transfer information.
6658
7408
  * @param params.transferInfo.amount - The amount value.
6659
7409
  * @param params.transferInfo.sourceDex - The source DEX for the transfer.
7410
+ * @returns Whether the balance check and any required transfer succeeded.
6660
7411
  * @private
6661
7412
  */
6662
7413
  async function _HyperLiquidProvider_handleHip3PostOrderRebalance(params) {
@@ -6685,16 +7436,20 @@ async function _HyperLiquidProvider_handleHip3PostOrderRebalance(params) {
6685
7436
  excessAmount: excessAmount.toFixed(2),
6686
7437
  destinationDex: transferInfo.sourceDex,
6687
7438
  });
6688
- await this.transferBetweenDexs({
7439
+ const transferResult = await this.transferBetweenDexs({
6689
7440
  sourceDex: dexName,
6690
7441
  destinationDex: transferInfo.sourceDex,
6691
7442
  amount: excessAmount.toFixed(USDC_DECIMALS),
6692
7443
  });
7444
+ if (!transferResult.success) {
7445
+ throw new Error(transferResult.error ?? PERPS_ERROR_CODES.TRANSFER_FAILED);
7446
+ }
6693
7447
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('✅ HyperLiquidProvider: Auto-rebalance completed', {
6694
7448
  transferredBack: excessAmount.toFixed(2),
6695
7449
  from: dexName,
6696
7450
  to: transferInfo.sourceDex,
6697
7451
  });
7452
+ return true;
6698
7453
  }
6699
7454
  catch (rebalanceError) {
6700
7455
  // Don't fail the order if rebalance fails (order already succeeded)
@@ -6703,6 +7458,7 @@ async function _HyperLiquidProvider_handleHip3PostOrderRebalance(params) {
6703
7458
  excessAmount: excessAmount.toFixed(2),
6704
7459
  note: 'Auto-rebalance failed - funds remain on HIP-3 DEX',
6705
7460
  }));
7461
+ return false;
6706
7462
  }
6707
7463
  }
6708
7464
  else {
@@ -6711,6 +7467,7 @@ async function _HyperLiquidProvider_handleHip3PostOrderRebalance(params) {
6711
7467
  threshold: minimumTransferThreshold.toFixed(2),
6712
7468
  note: 'Excess below minimum transfer threshold',
6713
7469
  });
7470
+ return true;
6714
7471
  }
6715
7472
  }
6716
7473
  catch (balanceCheckError) {
@@ -6719,6 +7476,7 @@ async function _HyperLiquidProvider_handleHip3PostOrderRebalance(params) {
6719
7476
  dex: dexName,
6720
7477
  note: 'Failed to verify post-order balance for auto-rebalance',
6721
7478
  }));
7479
+ return false;
6722
7480
  }
6723
7481
  }, _HyperLiquidProvider_handleHip3OrderRollback =
6724
7482
  /**
@@ -6902,7 +7660,10 @@ async function _HyperLiquidProvider_handleHip3PreOrder(params) {
6902
7660
  async function _HyperLiquidProvider_submitOrderWithRollback(params) {
6903
7661
  const { orders, grouping, isHip3Order, dexName, transferInfo, symbol } = params;
6904
7662
  const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
6905
- const builder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this);
7663
+ const builder = params.chargesMetamaskBuilderFee
7664
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this, params.builderFeeSetupContext ??
7665
+ (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureBuilderFeeSetup).call(this)))
7666
+ : undefined;
6906
7667
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Submitting order via asset ID routing', {
6907
7668
  symbol,
6908
7669
  assetId: orders[0].a,
@@ -6915,7 +7676,7 @@ async function _HyperLiquidProvider_submitOrderWithRollback(params) {
6915
7676
  const result = await exchangeClient.order({
6916
7677
  orders,
6917
7678
  grouping,
6918
- builder,
7679
+ ...(builder && { builder }),
6919
7680
  });
6920
7681
  if (result.status !== 'ok') {
6921
7682
  throw new Error(`Order failed: ${JSON.stringify(result)}`);
@@ -6990,18 +7751,22 @@ async function _HyperLiquidProvider_handleOrderError(params) {
6990
7751
  async function _HyperLiquidProvider_placeStrategyOrder(params, orderType) {
6991
7752
  // Captured before the shared preamble, not after it: that preamble awaits
6992
7753
  // asset info, validation, trading readiness and a leverage update, and a
6993
- // disconnect during any of them has to be seen by the chase registration
6994
- // at the end.
7754
+ // disconnect during any of them has to be seen before or after submission.
7755
+ const generation = __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f");
6995
7756
  if (orderType !== 'chase') {
6996
7757
  const context = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_prepareStrategyPlacement).call(this, params);
6997
- return orderType === 'twap'
6998
- ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_placeTwapOrder).call(this, params, context)
6999
- : await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_placeScaleOrder).call(this, params, context);
7758
+ return await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_submitPreparedStrategy).call(this, params, context, async () => {
7759
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7760
+ throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
7761
+ }
7762
+ return orderType === 'twap'
7763
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_placeTwapOrder).call(this, params, context, generation)
7764
+ : await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_placeScaleOrder).call(this, params, context, generation);
7765
+ });
7000
7766
  }
7001
7767
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") > 0) {
7002
7768
  throw new Error(PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7003
7769
  }
7004
- const generation = __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f");
7005
7770
  // The chase slot is claimed before the preamble, not after it: the preamble
7006
7771
  // completes the signing setup and can change the asset's leverage, and a
7007
7772
  // request that is going to be refused for exceeding the venue's cap must
@@ -7023,7 +7788,7 @@ async function _HyperLiquidProvider_placeStrategyOrder(params, orderType) {
7023
7788
  try {
7024
7789
  await __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f");
7025
7790
  const context = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_prepareStrategyPlacement).call(this, params);
7026
- return await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_startChaseSession).call(this, params, context, generation);
7791
+ return await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_submitPreparedStrategy).call(this, params, context, async () => await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_startChaseSession).call(this, params, context, generation));
7027
7792
  }
7028
7793
  finally {
7029
7794
  settlePlacement?.();
@@ -7033,6 +7798,74 @@ async function _HyperLiquidProvider_placeStrategyOrder(params, orderType) {
7033
7798
  __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resumeChaseTicksAfterPlacement).call(this);
7034
7799
  }
7035
7800
  }
7801
+ }, _HyperLiquidProvider_submitPreparedStrategy =
7802
+ /**
7803
+ * Submit a prepared strategy and settle any manual HIP-3 collateral move.
7804
+ *
7805
+ * Scale and Chase leave regular orders or fills on the venue, so the normal
7806
+ * HIP-3 post-order balance check can return any unreserved excess. Native
7807
+ * TWAP executes future slices, so its transferred collateral stays on the
7808
+ * routed DEX until cancellation confirms the schedule has stopped.
7809
+ *
7810
+ * @param params - Strategy order parameters.
7811
+ * @param context - Prepared placement and HIP-3 transfer context.
7812
+ * @param submit - Venue submission.
7813
+ * @returns The strategy placement result.
7814
+ */
7815
+ async function _HyperLiquidProvider_submitPreparedStrategy(params, context, submit) {
7816
+ const { dexName, network, transferInfo, userAddress } = context;
7817
+ let result;
7818
+ try {
7819
+ result = await submit();
7820
+ }
7821
+ catch (error) {
7822
+ if (dexName && transferInfo) {
7823
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3OrderRollback).call(this, { dexName, transferInfo });
7824
+ }
7825
+ throw error;
7826
+ }
7827
+ const hasVenueExposure = result.success === true ||
7828
+ result.orderId !== undefined ||
7829
+ (result.childOrderIds?.length ?? 0) > 0;
7830
+ if (dexName && transferInfo && !hasVenueExposure) {
7831
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3OrderRollback).call(this, { dexName, transferInfo });
7832
+ return result;
7833
+ }
7834
+ if (params.orderType === 'twap' && result.orderId) {
7835
+ __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").set(getTwapOrderScopeKey({
7836
+ network,
7837
+ userAddress,
7838
+ orderId: result.orderId,
7839
+ }), {
7840
+ symbol: params.symbol,
7841
+ ...(dexName && transferInfo
7842
+ ? { hip3Transfer: { dexName, transferInfo } }
7843
+ : {}),
7844
+ });
7845
+ return result;
7846
+ }
7847
+ if (params.orderType === 'chase' &&
7848
+ result.success &&
7849
+ result.orderId &&
7850
+ dexName &&
7851
+ transferInfo) {
7852
+ const session = __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").get(result.orderId);
7853
+ if (session) {
7854
+ session.hip3Transfer = { dexName, transferInfo };
7855
+ return result;
7856
+ }
7857
+ }
7858
+ if (params.orderType === 'chase' && hasVenueExposure) {
7859
+ // A stale placement can fail after its child rests and the best-effort
7860
+ // retraction is refused. With no session left to own that child, keep
7861
+ // manual HIP-3 collateral on its DEX until the caller cancels the
7862
+ // reported exchange ID.
7863
+ return result;
7864
+ }
7865
+ if (dexName && transferInfo) {
7866
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3PostOrderRebalance).call(this, { dexName, transferInfo });
7867
+ }
7868
+ return result;
7036
7869
  }, _HyperLiquidProvider_prepareStrategyPlacement =
7037
7870
  /**
7038
7871
  * Run the shared preamble every strategy placement needs.
@@ -7046,13 +7879,6 @@ async function _HyperLiquidProvider_placeStrategyOrder(params, orderType) {
7046
7879
  */
7047
7880
  async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7048
7881
  const { dex: dexName } = parseAssetName(params.symbol);
7049
- // A HIP-3 order is preceded by a margin transfer onto the builder DEX and
7050
- // followed by a rebalance, both of which are wired into the single-order
7051
- // submit path and its rollback. Rather than half-support that here, a
7052
- // strategy placement on a HIP-3 market is refused outright.
7053
- if (dexName !== null) {
7054
- throw new Error(PERPS_ERROR_CODES.ORDER_STRATEGY_MARKET_UNSUPPORTED);
7055
- }
7056
7882
  const { assetInfo, currentPrice, meta } = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetInfo).call(this, {
7057
7883
  symbol: params.symbol,
7058
7884
  dexName,
@@ -7113,7 +7939,10 @@ async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7113
7939
  }
7114
7940
  // Kept after validation so an invalid strategy order never triggers the
7115
7941
  // signature prompts in trading setup — same ordering as `placeOrder`.
7116
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
7942
+ const { chargesMetamaskBuilderFee } = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, params);
7943
+ const builderFeeSetupContext = chargesMetamaskBuilderFee
7944
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: true })
7945
+ : await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
7117
7946
  const assetId = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
7118
7947
  symbol: params.symbol,
7119
7948
  dexName,
@@ -7124,12 +7953,38 @@ async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7124
7953
  assetId,
7125
7954
  leverage: params.leverage,
7126
7955
  });
7956
+ const builder = builderFeeSetupContext
7957
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this, builderFeeSetupContext)
7958
+ : undefined;
7959
+ const network = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode() ? 'testnet' : 'mainnet';
7960
+ const userAddress = builderFeeSetupContext?.userAddress ??
7961
+ (await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault());
7962
+ let transferInfo = null;
7963
+ if (dexName) {
7964
+ const orderPrice = ladder === undefined
7965
+ ? effectivePrice
7966
+ : Math.max(...ladder.prices.map(Number.parseFloat));
7967
+ const hip3Result = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3PreOrder).call(this, {
7968
+ dexName,
7969
+ symbol: params.symbol,
7970
+ orderPrice,
7971
+ positionSize: parseFloat(formattedSize),
7972
+ leverage: params.leverage ?? assetInfo.maxLeverage ?? 1,
7973
+ isBuy: params.isBuy,
7974
+ maxLeverage: assetInfo.maxLeverage,
7975
+ });
7976
+ transferInfo = hip3Result.transferInfo;
7977
+ }
7127
7978
  return {
7128
7979
  assetId,
7129
7980
  szDecimals: assetInfo.szDecimals,
7130
- finalPositionSize,
7131
7981
  formattedSize,
7132
7982
  ladder,
7983
+ builder,
7984
+ dexName,
7985
+ network,
7986
+ transferInfo,
7987
+ userAddress,
7133
7988
  };
7134
7989
  }, _HyperLiquidProvider_getMinimumOrderSize = function _HyperLiquidProvider_getMinimumOrderSize() {
7135
7990
  return __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode()
@@ -7137,11 +7992,17 @@ async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7137
7992
  : TRADING_DEFAULTS.amount.mainnet;
7138
7993
  }, _HyperLiquidProvider_buildScaleLadder = function _HyperLiquidProvider_buildScaleLadder(options) {
7139
7994
  const { params, szDecimals, finalPositionSize } = options;
7140
- const count = params.scaleNumOrders;
7995
+ const { scaleMinPrice, scaleMaxPrice, scaleNumOrders } = params;
7996
+ if (scaleMinPrice === undefined || scaleMaxPrice === undefined) {
7997
+ throw new Error(PERPS_ERROR_CODES.ORDER_SCALE_RANGE_REQUIRED);
7998
+ }
7999
+ if (scaleNumOrders === undefined) {
8000
+ throw new Error(PERPS_ERROR_CODES.ORDER_SCALE_COUNT_INVALID);
8001
+ }
7141
8002
  const prices = computeScalePriceLadder({
7142
- minPrice: parseFloat(params.scaleMinPrice),
7143
- maxPrice: parseFloat(params.scaleMaxPrice),
7144
- count,
8003
+ minPrice: parseFloat(scaleMinPrice),
8004
+ maxPrice: parseFloat(scaleMaxPrice),
8005
+ count: scaleNumOrders,
7145
8006
  }).map((price) => formatHyperLiquidPrice({ price, szDecimals }));
7146
8007
  if (new Set(prices).size !== prices.length) {
7147
8008
  throw new Error(PERPS_ERROR_CODES.ORDER_SCALE_RANGE_INVALID);
@@ -7150,7 +8011,7 @@ async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7150
8011
  // which a skew weighted far enough from even can do on its own.
7151
8012
  const sizes = splitScaleSizes({
7152
8013
  totalSize: finalPositionSize,
7153
- count,
8014
+ count: scaleNumOrders,
7154
8015
  szDecimals,
7155
8016
  skew: params.scaleSkew,
7156
8017
  });
@@ -7170,16 +8031,26 @@ async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7170
8031
  *
7171
8032
  * @param params - Order parameters.
7172
8033
  * @param context - Prepared asset and sizing context.
8034
+ * @param generation - Teardown generation captured before preparation.
7173
8035
  * @returns A promise that resolves to the result.
7174
8036
  */
7175
- async function _HyperLiquidProvider_placeTwapOrder(params, context) {
8037
+ async function _HyperLiquidProvider_placeTwapOrder(params, context, generation) {
7176
8038
  const { assetId, formattedSize } = context;
8039
+ const durationMinutes = params.twapDuration;
8040
+ if (durationMinutes === undefined) {
8041
+ throw new Error(PERPS_ERROR_CODES.ORDER_TWAP_DURATION_REQUIRED);
8042
+ }
8043
+ if (!Number.isSafeInteger(durationMinutes) ||
8044
+ durationMinutes < HYPERLIQUID_TWAP_LIMITS.MinDurationMinutes ||
8045
+ durationMinutes > HYPERLIQUID_TWAP_LIMITS.MaxDurationMinutes) {
8046
+ throw new Error(PERPS_ERROR_CODES.ORDER_TWAP_DURATION_INVALID);
8047
+ }
7177
8048
  const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
7178
8049
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Submitting TWAP order', {
7179
8050
  symbol: params.symbol,
7180
8051
  assetId,
7181
8052
  size: formattedSize,
7182
- durationMinutes: params.twapDuration,
8053
+ durationMinutes,
7183
8054
  randomize: params.twapRandomize ?? false,
7184
8055
  });
7185
8056
  const result = await exchangeClient.twapOrder({
@@ -7188,9 +8059,7 @@ async function _HyperLiquidProvider_placeTwapOrder(params, context) {
7188
8059
  b: params.isBuy,
7189
8060
  s: formattedSize,
7190
8061
  r: params.reduceOnly ?? false,
7191
- // Validation has already established this is a whole number of minutes
7192
- // inside the venue's bounds.
7193
- m: params.twapDuration,
8062
+ m: durationMinutes,
7194
8063
  t: params.twapRandomize ?? false,
7195
8064
  },
7196
8065
  });
@@ -7198,15 +8067,44 @@ async function _HyperLiquidProvider_placeTwapOrder(params, context) {
7198
8067
  throw new Error(`TWAP order failed: ${JSON.stringify(result)}`);
7199
8068
  }
7200
8069
  const status = result.response?.data?.status;
7201
- if (!isStatusObject(status) || !hasProperty(status, 'running')) {
7202
- const rawError = status?.error ??
7203
- 'TWAP order rejected';
8070
+ if (!isStatusObject(status)) {
8071
+ throw new Error('TWAP order rejected');
8072
+ }
8073
+ const rawError = typeof status.error === 'string' ? status.error : 'TWAP order rejected';
8074
+ const { running } = status;
8075
+ if (!isStatusObject(running) ||
8076
+ typeof running.twapId !== 'number' ||
8077
+ !Number.isSafeInteger(running.twapId) ||
8078
+ running.twapId < 0) {
7204
8079
  throw new Error(rawError);
7205
8080
  }
7206
- const running = status.running;
8081
+ const orderId = running.twapId.toString();
8082
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
8083
+ let remainsLive = true;
8084
+ try {
8085
+ const cancelResult = await exchangeClient.twapCancel({
8086
+ a: assetId,
8087
+ t: running.twapId,
8088
+ });
8089
+ remainsLive =
8090
+ classifyCancelStatus(cancelResult.response?.data?.status) ===
8091
+ CancelChildOutcome.Refused;
8092
+ }
8093
+ catch (error) {
8094
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Stale TWAP placement could not be retracted', {
8095
+ error: ensureError(error, 'HyperLiquidProvider.placeTwapOrder.retract').message,
8096
+ orderId,
8097
+ });
8098
+ }
8099
+ return createErrorResult(new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE), {
8100
+ success: false,
8101
+ submittedSize: formattedSize,
8102
+ ...(remainsLive && { orderId }),
8103
+ });
8104
+ }
7207
8105
  return {
7208
8106
  success: true,
7209
- orderId: running.twapId.toString(),
8107
+ orderId,
7210
8108
  submittedSize: formattedSize,
7211
8109
  };
7212
8110
  }, _HyperLiquidProvider_placeScaleOrder =
@@ -7215,22 +8113,25 @@ async function _HyperLiquidProvider_placeTwapOrder(params, context) {
7215
8113
  *
7216
8114
  * The whole ladder goes in a single `order` action, which is one round trip
7217
8115
  * and one signature rather than one per rung. It is **not** atomic: an `na`
7218
- * grouping evaluates each entry independently, so the response can report
7219
- * some rungs resting and others rejected. A partial ladder is reported as
7220
- * such — `submittedSize` covers only the rungs that actually rested — rather
7221
- * than as a full submission.
8116
+ * grouping evaluates each entry independently. Every rung must either rest
8117
+ * or fill; otherwise all known resting rungs are retracted before failure.
7222
8118
  *
7223
8119
  * @param params - Order parameters.
7224
8120
  * @param context - Prepared asset and sizing context.
8121
+ * @param generation - Teardown generation captured before preparation.
7225
8122
  * @returns A promise that resolves to the result.
7226
8123
  */
7227
- async function _HyperLiquidProvider_placeScaleOrder(params, context) {
7228
- const { assetId, szDecimals, formattedSize, ladder } = context;
8124
+ async function _HyperLiquidProvider_placeScaleOrder(params, context, generation) {
8125
+ const { assetId, formattedSize, ladder, builder } = context;
7229
8126
  // Built and validated in `#prepareStrategyPlacement`, before anything was
7230
8127
  // signed, so what is submitted here is exactly what the minimums were
7231
8128
  // applied to.
8129
+ if (!ladder) {
8130
+ throw new Error(PERPS_ERROR_CODES.ORDER_SCALE_RANGE_REQUIRED);
8131
+ }
7232
8132
  const { prices, sizes } = ladder;
7233
8133
  const count = prices.length;
8134
+ const { groupId, clientOrderIds } = createScaleOrderIdentity(count);
7234
8135
  const orders = prices.map((price, index) => ({
7235
8136
  a: assetId,
7236
8137
  b: params.isBuy,
@@ -7238,6 +8139,7 @@ async function _HyperLiquidProvider_placeScaleOrder(params, context) {
7238
8139
  s: sizes[index],
7239
8140
  r: params.reduceOnly ?? false,
7240
8141
  t: { limit: { tif: 'Gtc' } },
8142
+ c: clientOrderIds[index],
7241
8143
  }));
7242
8144
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Submitting scale ladder', {
7243
8145
  symbol: params.symbol,
@@ -7250,50 +8152,81 @@ async function _HyperLiquidProvider_placeScaleOrder(params, context) {
7250
8152
  const result = await exchangeClient.order({
7251
8153
  orders,
7252
8154
  grouping: 'na',
7253
- builder: await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this),
8155
+ ...(builder && { builder }),
7254
8156
  });
7255
- if (result.status !== 'ok') {
7256
- throw new Error(`Scale order failed: ${JSON.stringify(result)}`);
7257
- }
7258
8157
  const statuses = result.response?.data?.statuses ?? [];
7259
- // Which rung each ID came from matters: `submittedSize` has to add up the
7260
- // slices that actually rested, not the total that was asked for.
7261
- const restedRungs = statuses
7262
- .map((status, index) => ({
7263
- orderId: __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderIdFromStatus).call(this, status),
7264
- size: sizes[index],
7265
- }))
7266
- .filter((rung) => rung.orderId !== undefined);
7267
- // A ladder that came back with no IDs at all rested nothing; reporting it
7268
- // as a success would hand the caller a handle that cancels nothing.
7269
- if (restedRungs.length === 0) {
7270
- throw new Error(`Scale order rejected: ${JSON.stringify(result.response?.data)}`);
7271
- }
7272
- const childOrderIds = restedRungs.map((rung) => rung.orderId);
7273
- const groupId = generatePerpsId('scale');
7274
- __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").set(groupId, { assetId, orderIds: childOrderIds });
7275
- // The batch is not atomic, so a ladder can come back part-rested. Reporting
7276
- // the requested total as submitted would tell the caller they are exposed
7277
- // for more than they are.
7278
- const isPartial = restedRungs.length < count;
7279
- if (isPartial) {
7280
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Scale ladder only partly rested', {
7281
- groupId,
7282
- rested: restedRungs.length,
8158
+ const outcomes = statuses
8159
+ .slice(0, count)
8160
+ .map((status) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderPlacementOutcome).call(this, status));
8161
+ const acceptedCount = outcomes.filter((outcome) => outcome !== undefined).length;
8162
+ const restingChildOrderIds = outcomes.flatMap((outcome) => outcome?.state === 'resting' ? [outcome.orderId] : []);
8163
+ const filledChildOrderIds = outcomes.flatMap((outcome) => outcome?.state === 'filled' ? [outcome.orderId] : []);
8164
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
8165
+ const remainingOrderIds = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequests).call(this, exchangeClient, restingChildOrderIds.map((orderId) => ({
8166
+ a: assetId,
8167
+ o: Number(orderId),
8168
+ })));
8169
+ const recoverableOrderIds = [
8170
+ ...filledChildOrderIds,
8171
+ ...remainingOrderIds.map(String),
8172
+ ];
8173
+ return createErrorResult(new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE), {
8174
+ success: false,
8175
+ submittedSize: formattedSize,
8176
+ ...(recoverableOrderIds.length > 0 && {
8177
+ childOrderIds: recoverableOrderIds,
8178
+ }),
8179
+ });
8180
+ }
8181
+ if (result.status !== 'ok' ||
8182
+ statuses.length !== count ||
8183
+ acceptedCount !== count) {
8184
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Scale ladder was not fully accepted', {
8185
+ accepted: acceptedCount,
8186
+ filled: filledChildOrderIds.length,
8187
+ resting: restingChildOrderIds.length,
7283
8188
  requested: count,
7284
8189
  statuses,
7285
8190
  });
8191
+ const remainingOrderIds = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequests).call(this, exchangeClient, restingChildOrderIds.map((orderId) => ({
8192
+ a: assetId,
8193
+ o: Number(orderId),
8194
+ })));
8195
+ const recoverableOrderIds = [
8196
+ ...filledChildOrderIds,
8197
+ ...remainingOrderIds.map(String),
8198
+ ];
8199
+ if (remainingOrderIds.length > 0) {
8200
+ const remainingRestingOrderIds = remainingOrderIds.map(String);
8201
+ __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").set(groupId, {
8202
+ symbol: params.symbol,
8203
+ orderIds: remainingRestingOrderIds,
8204
+ });
8205
+ return createErrorResult(new Error(PERPS_ERROR_CODES.ORDER_STRATEGY_CANCEL_INCOMPLETE), {
8206
+ success: false,
8207
+ orderId: groupId,
8208
+ childOrderIds: recoverableOrderIds,
8209
+ submittedSize: formattedSize,
8210
+ });
8211
+ }
8212
+ if (recoverableOrderIds.length > 0) {
8213
+ return createErrorResult(new Error(PERPS_ERROR_CODES.ORDER_REJECTED), {
8214
+ success: false,
8215
+ childOrderIds: recoverableOrderIds,
8216
+ submittedSize: formattedSize,
8217
+ });
8218
+ }
8219
+ throw new Error(PERPS_ERROR_CODES.ORDER_REJECTED);
7286
8220
  }
8221
+ __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").set(groupId, {
8222
+ symbol: params.symbol,
8223
+ orderIds: restingChildOrderIds,
8224
+ });
7287
8225
  return {
7288
8226
  success: true,
7289
8227
  orderId: groupId,
7290
- childOrderIds,
7291
- submittedSize: isPartial
7292
- ? formatHyperLiquidSize({
7293
- size: restedRungs.reduce((total, rung) => total + parseFloat(rung.size), 0),
7294
- szDecimals,
7295
- })
7296
- : formattedSize,
8228
+ childOrderIds: restingChildOrderIds,
8229
+ submittedSize: formattedSize,
7297
8230
  };
7298
8231
  }, _HyperLiquidProvider_startChaseSession =
7299
8232
  /**
@@ -7308,12 +8241,12 @@ async function _HyperLiquidProvider_placeScaleOrder(params, context) {
7308
8241
  * @returns A promise that resolves to the result.
7309
8242
  */
7310
8243
  async function _HyperLiquidProvider_startChaseSession(params, context, generation) {
7311
- const { assetId, szDecimals, formattedSize } = context;
8244
+ const { assetId, szDecimals, formattedSize, builder } = context;
7312
8245
  // The preamble is several round trips long. A disconnect during it has
7313
8246
  // already torn down everything this session would run on, so the chase
7314
8247
  // stops here rather than reading the book and putting a fresh order on it
7315
8248
  // for a provider that no longer exists.
7316
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
8249
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7317
8250
  throw new Error(PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7318
8251
  }
7319
8252
  let quotePrice = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getChaseQuotePrice).call(this, {
@@ -7321,8 +8254,9 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7321
8254
  isBuy: params.isBuy,
7322
8255
  szDecimals,
7323
8256
  });
7324
- if (quotePrice === 'gone') {
7325
- // No own order to be gone on a first placement; narrows the union.
8257
+ if (quotePrice === CHASE_ORDER_STATUS_UNAVAILABLE ||
8258
+ typeof quotePrice !== 'string') {
8259
+ // A first placement has no child whose terminal state could be read.
7326
8260
  throw new Error(PERPS_ERROR_CODES.ORDER_CHASE_TOUCH_UNAVAILABLE);
7327
8261
  }
7328
8262
  // The book read is a round trip of its own, so the check above does not
@@ -7331,12 +8265,7 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7331
8265
  // before the next one, leaving one window that cannot be closed from here
7332
8266
  // — a disconnect arriving while the submission itself is in flight, which
7333
8267
  // the check after it handles.
7334
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
7335
- throw new Error(PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7336
- }
7337
- // Read once, while the caller's fee-source context is still set.
7338
- const builder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this);
7339
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
8268
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7340
8269
  throw new Error(PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7341
8270
  }
7342
8271
  // Held onto rather than looked up again after the submission returns.
@@ -7354,8 +8283,7 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7354
8283
  price: quotePrice,
7355
8284
  size: formattedSize,
7356
8285
  reduceOnly: params.reduceOnly ?? false,
7357
- builderFee: builder.f,
7358
- builderAddress: builder.b,
8286
+ builder,
7359
8287
  exchangeClient: placingClient,
7360
8288
  });
7361
8289
  break;
@@ -7365,7 +8293,7 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7365
8293
  !isRetryableChasePlacementError(error)) {
7366
8294
  throw error;
7367
8295
  }
7368
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
8296
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7369
8297
  throw new Error(PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7370
8298
  }
7371
8299
  const refreshedQuote = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getChaseQuotePrice).call(this, {
@@ -7373,10 +8301,11 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7373
8301
  isBuy: params.isBuy,
7374
8302
  szDecimals,
7375
8303
  });
7376
- if (refreshedQuote === 'gone') {
8304
+ if (refreshedQuote === CHASE_ORDER_STATUS_UNAVAILABLE ||
8305
+ typeof refreshedQuote !== 'string') {
7377
8306
  throw new Error(PERPS_ERROR_CODES.ORDER_CHASE_TOUCH_UNAVAILABLE);
7378
8307
  }
7379
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
8308
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7380
8309
  throw new Error(PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7381
8310
  }
7382
8311
  quotePrice = refreshedQuote;
@@ -7404,11 +8333,11 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7404
8333
  maxDistanceBps: params.chaseMaxDistanceBps,
7405
8334
  intervalMs,
7406
8335
  lastSnapshotSizeRefreshAt: 0,
7407
- builderFee: builder.f,
7408
- builderAddress: builder.b,
7409
- deadline: Date.now() +
7410
- (params.chaseMaxDurationMs ?? CHASE_ORDER_CONFIG.DefaultMaxDurationMs),
7411
- maxRepricings: params.chaseMaxRepricings ?? CHASE_ORDER_CONFIG.DefaultMaxRepricings,
8336
+ builder,
8337
+ deadline: params.chaseMaxDurationMs === undefined
8338
+ ? Number.POSITIVE_INFINITY
8339
+ : Date.now() + params.chaseMaxDurationMs,
8340
+ maxRepricings: params.chaseMaxRepricings ?? Number.POSITIVE_INFINITY,
7412
8341
  repricings: 0,
7413
8342
  timer: null,
7414
8343
  active: true,
@@ -7427,7 +8356,7 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7427
8356
  // and the failure is then true of the venue as well as of this provider.
7428
8357
  // Best-effort still: the transport underneath that client may already be
7429
8358
  // closing, so a cancel that does not land falls back to reporting the id.
7430
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
8359
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7431
8360
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase placement outlived its provider', {
7432
8361
  orderId,
7433
8362
  restingPrice: quotePrice,
@@ -7439,7 +8368,9 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7439
8368
  // that is no longer on the book would send the caller to cancel
7440
8369
  // something already gone; naming one that still rests is the only
7441
8370
  // route left to it.
7442
- ...(outcome === 'refused' ? { childOrderIds: [orderId] } : {}),
8371
+ ...(outcome === CancelChildOutcome.Refused
8372
+ ? { childOrderIds: [orderId] }
8373
+ : {}),
7443
8374
  submittedSize: formattedSize,
7444
8375
  });
7445
8376
  }
@@ -7475,8 +8406,8 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7475
8406
  * @param params.own.orderId - Exchange ID of that order.
7476
8407
  * @param params.own.price - Price that order rests at.
7477
8408
  * @param params.own.size - Size it was last placed for.
7478
- * @returns The formatted price the chase should rest at, or `'gone'` when the
7479
- * order it was chasing is no longer live.
8409
+ * @returns The formatted price, a terminal child result, or the status
8410
+ * unavailable sentinel when the child may still be live.
7480
8411
  */
7481
8412
  async function _HyperLiquidProvider_getChaseQuotePrice(params) {
7482
8413
  const { symbol, isBuy, szDecimals, own } = params;
@@ -7491,8 +8422,9 @@ async function _HyperLiquidProvider_getChaseQuotePrice(params) {
7491
8422
  callerOrderId: own?.orderId,
7492
8423
  ownSide,
7493
8424
  });
7494
- if (netting === 'gone') {
7495
- return 'gone';
8425
+ if (netting === CHASE_ORDER_STATUS_UNAVAILABLE ||
8426
+ !(netting instanceof Map)) {
8427
+ return netting;
7496
8428
  }
7497
8429
  const bestBid = readBestExternalPrice(book?.levels?.[0], netting);
7498
8430
  const bestAsk = readBestExternalPrice(book?.levels?.[1], netting);
@@ -7510,9 +8442,7 @@ async function _HyperLiquidProvider_getChaseQuotePrice(params) {
7510
8442
  * @param params.price - Formatted limit price.
7511
8443
  * @param params.size - Formatted size.
7512
8444
  * @param params.reduceOnly - Whether the order may only reduce a position.
7513
- * @param params.builderFee - Builder fee, in tenths of a basis point, captured
7514
- * when the session started so replacements keep the rate they were quoted at.
7515
- * @param params.builderAddress - Builder address captured with the fee.
8445
+ * @param params.builder - Builder context captured when the session started.
7516
8446
  * @param params.exchangeClient - Client to submit through. Passed in rather
7517
8447
  * than looked up here so a first placement can keep the instance it signed
7518
8448
  * with, which is the only one that can take the order back once `disconnect`
@@ -7534,10 +8464,7 @@ async function _HyperLiquidProvider_restChaseOrder(params) {
7534
8464
  },
7535
8465
  ],
7536
8466
  grouping: 'na',
7537
- builder: {
7538
- b: params.builderAddress,
7539
- f: params.builderFee,
7540
- },
8467
+ ...(params.builder && { builder: params.builder }),
7541
8468
  });
7542
8469
  if (result.status !== 'ok') {
7543
8470
  throw new Error(`Chase order failed: ${JSON.stringify(result)}`);
@@ -7549,25 +8476,21 @@ async function _HyperLiquidProvider_restChaseOrder(params) {
7549
8476
  return orderId;
7550
8477
  }, _HyperLiquidProvider_scheduleChaseTick = function _HyperLiquidProvider_scheduleChaseTick(sessionId) {
7551
8478
  const session = __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").get(sessionId);
7552
- if (!session?.active) {
8479
+ if (!session?.active || session.timer !== null) {
7553
8480
  return;
7554
8481
  }
7555
8482
  const timer = setTimeout(() => {
7556
8483
  session.timer = null;
7557
- __classPrivateFieldSet(this, _HyperLiquidProvider_chaseTickQueue, __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f").then(async () => {
7558
- // A rejected tick must not stop the session from being cancellable, and
7559
- // must not surface as an unhandled rejection either.
7560
- try {
7561
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_runChaseTick).call(this, sessionId);
7562
- }
7563
- catch (error) {
7564
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase tick failed', {
7565
- sessionId,
7566
- error: ensureError(error, 'HyperLiquidProvider.chaseTick').message,
7567
- });
7568
- __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_recoverFailedChaseTick).call(this, sessionId);
7569
- }
7570
- return undefined;
8484
+ __classPrivateFieldSet(this, _HyperLiquidProvider_chaseTickQueue, __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f")
8485
+ .then(() => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_runChaseTick).call(this, sessionId))
8486
+ .catch((error) => {
8487
+ // Resolve the shared queue after every failure. Otherwise one
8488
+ // rejected tick prevents all later ticks and teardown from running.
8489
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase tick failed', {
8490
+ sessionId,
8491
+ error: ensureError(error, 'HyperLiquidProvider.chaseTick').message,
8492
+ });
8493
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_recoverFailedChaseTick).call(this, sessionId);
7571
8494
  }), "f");
7572
8495
  }, session.intervalMs);
7573
8496
  // Node keeps the process alive for a pending timer; a background chase is
@@ -7599,6 +8522,12 @@ async function _HyperLiquidProvider_restChaseOrder(params) {
7599
8522
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase ended with nothing resting', {
7600
8523
  sessionId,
7601
8524
  });
8525
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session).catch((error) => {
8526
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase collateral cleanup failed', {
8527
+ sessionId,
8528
+ error: ensureError(error, 'HyperLiquidProvider.recoverFailedChaseTick').message,
8529
+ });
8530
+ });
7602
8531
  return;
7603
8532
  }
7604
8533
  __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_scheduleChaseTick).call(this, sessionId);
@@ -7632,6 +8561,7 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7632
8561
  session.active = false;
7633
8562
  session.status = CHASE_ORDER_STATUS.DurationReached;
7634
8563
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase session window closed', { sessionId });
8564
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
7635
8565
  return;
7636
8566
  }
7637
8567
  const rawQuotePrice = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getChaseQuotePrice).call(this, {
@@ -7646,16 +8576,29 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7646
8576
  }
7647
8577
  : undefined,
7648
8578
  });
7649
- if (rawQuotePrice === 'gone') {
7650
- // The order left the book without the loop seeing it — it filled. Ending
7651
- // here releases its slot against the venue's concurrency cap instead of
7652
- // holding one until the window closes.
8579
+ if (typeof rawQuotePrice !== 'string') {
8580
+ // The order left the book without the loop seeing it. Preserve the venue
8581
+ // status so an external cancellation is not reported as a fill.
7653
8582
  session.orderId = null;
7654
8583
  session.active = false;
7655
- session.status = CHASE_ORDER_STATUS.Filled;
7656
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order filled between ticks', {
8584
+ session.status = rawQuotePrice.status;
8585
+ session.size = rawQuotePrice.remainingSize ?? '0';
8586
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order ended between ticks', {
8587
+ sessionId,
8588
+ status: session.status,
8589
+ });
8590
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
8591
+ return;
8592
+ }
8593
+ if (rawQuotePrice === CHASE_ORDER_STATUS_UNAVAILABLE) {
8594
+ // The child may still be live. Leave it untouched and retry on the next
8595
+ // normal interval instead of permanently stranding the Chase after one
8596
+ // short status outage.
8597
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order status unavailable', {
7657
8598
  sessionId,
8599
+ orderId: session.orderId,
7658
8600
  });
8601
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_scheduleChaseTick).call(this, sessionId);
7659
8602
  return;
7660
8603
  }
7661
8604
  const arrivalPrice = Number.parseFloat(session.arrivalPrice);
@@ -7713,12 +8656,12 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7713
8656
  // A refused cancel leaves the child resting. Preserve its ID so the
7714
8657
  // caller that stopped this session can report an incomplete cancel and
7715
8658
  // retry it instead of orphaning the order.
7716
- if (outcome !== 'refused') {
8659
+ if (outcome !== CancelChildOutcome.Refused) {
7717
8660
  session.orderId = null;
7718
8661
  }
7719
8662
  return;
7720
8663
  }
7721
- if (outcome === 'refused') {
8664
+ if (outcome === CancelChildOutcome.Refused) {
7722
8665
  // The exchange kept the order, so it is still resting at the old price.
7723
8666
  // Placing the replacement anyway would double the position. Leave it
7724
8667
  // alone and try again on the next tick, which is why this falls through
@@ -7729,25 +8672,36 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7729
8672
  });
7730
8673
  reachedMaxDistance = false;
7731
8674
  }
7732
- else if (outcome === 'gone') {
8675
+ else if (outcome === CancelChildOutcome.Gone) {
7733
8676
  // The order left the book between ticks: it filled, or something else
7734
8677
  // cancelled it. There is nothing left to chase.
7735
8678
  const goneOrderId = session.orderId;
7736
- const remaining = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, goneOrderId);
7737
8679
  session.orderId = null;
7738
8680
  session.active = false;
7739
- session.status =
7740
- remaining === null
7741
- ? CHASE_ORDER_STATUS.Filled
7742
- : CHASE_ORDER_STATUS.Failed;
7743
- if (remaining !== null) {
7744
- session.size = remaining;
8681
+ let remainder;
8682
+ try {
8683
+ remainder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, goneOrderId, CHASE_ORDER_STATUS_RETRY_COUNT);
8684
+ }
8685
+ catch (error) {
8686
+ session.status = CHASE_ORDER_STATUS.Failed;
8687
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order status unavailable', {
8688
+ sessionId,
8689
+ orderId: goneOrderId,
8690
+ error: ensureError(error, 'HyperLiquidProvider.chaseGoneOrderStatus').message,
8691
+ });
8692
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
8693
+ return;
8694
+ }
8695
+ session.status = remainder.terminalStatus ?? CHASE_ORDER_STATUS.Failed;
8696
+ if (remainder.remainingSize !== null) {
8697
+ session.size = remainder.remainingSize;
7745
8698
  }
7746
8699
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order no longer resting', {
7747
8700
  sessionId,
7748
8701
  status: session.status,
7749
8702
  remainingSize: session.size,
7750
8703
  });
8704
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
7751
8705
  return;
7752
8706
  }
7753
8707
  else {
@@ -7761,25 +8715,44 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7761
8715
  // No further fills can reach a cancelled order, so what it did not fill
7762
8716
  // is now fixed. Sampling before the cancel would have left a window in
7763
8717
  // which a fill lands and is then re-placed.
7764
- const remaining = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, cancelledOrderId);
8718
+ let remainder;
8719
+ try {
8720
+ remainder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, cancelledOrderId, CHASE_ORDER_STATUS_RETRY_COUNT);
8721
+ }
8722
+ catch (error) {
8723
+ session.replacingOrderId = null;
8724
+ throw error;
8725
+ }
7765
8726
  // Another round trip, another window for a cancel to land. By now the
7766
8727
  // old order is already off the book, so a cancel that arrived during it
7767
8728
  // found nothing to cancel and reported success — resting a replacement
7768
8729
  // after that would put an order on the book the caller believes is gone
7769
8730
  // and has no handle for.
7770
8731
  if (!session.active || !__classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").has(sessionId)) {
8732
+ session.replacingOrderId = null;
7771
8733
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase cancelled mid-reprice', {
7772
8734
  sessionId,
7773
8735
  });
7774
8736
  return;
7775
8737
  }
7776
- if (remaining === null) {
7777
- // It filled completely between ticks; there is nothing left to chase.
8738
+ if (remainder.remainingSize === null ||
8739
+ (remainder.terminalStatus !== null &&
8740
+ remainder.terminalStatus !== CHASE_ORDER_STATUS.Canceled)) {
8741
+ // Nothing is left to chase. Preserve whether the venue actually
8742
+ // confirmed a fill rather than labelling every zero remainder filled.
8743
+ session.replacingOrderId = null;
7778
8744
  session.active = false;
7779
- session.status = CHASE_ORDER_STATUS.Filled;
7780
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order fully filled', { sessionId });
8745
+ session.size = remainder.remainingSize ?? '0';
8746
+ session.status =
8747
+ remainder.terminalStatus ?? CHASE_ORDER_STATUS.Failed;
8748
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order has no remainder', {
8749
+ sessionId,
8750
+ status: session.status,
8751
+ });
8752
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
7781
8753
  return;
7782
8754
  }
8755
+ const remaining = remainder.remainingSize;
7783
8756
  // Published *before* the placement starts, not from the promise it
7784
8757
  // returns: the window opens the moment `#restChaseOrder` is entered, so
7785
8758
  // deriving the marker from its return value would leave the synchronous
@@ -7798,8 +8771,7 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7798
8771
  price: quotePrice,
7799
8772
  size: remaining,
7800
8773
  reduceOnly: session.reduceOnly,
7801
- builderFee: session.builderFee,
7802
- builderAddress: session.builderAddress,
8774
+ builder: session.builder,
7803
8775
  // A running session is on a live provider, so the current client is
7804
8776
  // the right one; only the first placement has a teardown to survive.
7805
8777
  exchangeClient: __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient(),
@@ -7837,7 +8809,7 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7837
8809
  }
7838
8810
  }
7839
8811
  }
7840
- if (reachedMaxDistance) {
8812
+ if (reachedMaxDistance && session.maxDistanceBps !== undefined) {
7841
8813
  session.active = false;
7842
8814
  session.status = CHASE_ORDER_STATUS.MaxDistanceReached;
7843
8815
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase max distance reached', {
@@ -7845,12 +8817,24 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7845
8817
  restingPrice: session.restingPrice,
7846
8818
  maxDistanceBps: session.maxDistanceBps,
7847
8819
  });
8820
+ __classPrivateFieldGet(this, _HyperLiquidProvider_onChaseOrderMaxDistanceReached, "f")?.call(this, {
8821
+ handle: sessionId,
8822
+ symbol: session.symbol,
8823
+ side: session.isBuy ? 'buy' : 'sell',
8824
+ restingOrderId: session.orderId,
8825
+ restingPrice: session.restingPrice,
8826
+ maxDistanceBps: session.maxDistanceBps,
8827
+ timestamp: Date.now(),
8828
+ providerId: PROVIDER_CONFIG.DefaultProvider,
8829
+ });
8830
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
7848
8831
  return;
7849
8832
  }
7850
8833
  if (session.repricings >= session.maxRepricings) {
7851
8834
  session.active = false;
7852
8835
  session.status = CHASE_ORDER_STATUS.RepricingLimitReached;
7853
8836
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase repricing cap reached', { sessionId });
8837
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
7854
8838
  return;
7855
8839
  }
7856
8840
  __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_scheduleChaseTick).call(this, sessionId);
@@ -7873,21 +8857,24 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7873
8857
  * @param params.isBuy - Side being quoted.
7874
8858
  * @param params.callerOrderId - The asking session's order, if it has one.
7875
8859
  * @param params.ownSide - The side of the book those orders rest on.
7876
- * @returns Our resting size at each price, or `'gone'` when the asking
7877
- * session's own order is no longer live.
8860
+ * @returns Our resting size at each price, the asking child's terminal
8861
+ * result, or the unavailable sentinel.
7878
8862
  */
7879
8863
  async function _HyperLiquidProvider_resolveOwnRestingSizes(params) {
7880
8864
  const { symbol, isBuy, callerOrderId, ownSide } = params;
7881
8865
  // Every chase this provider is running on this side, the caller included.
7882
- const ourOrders = [...__classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").values()]
7883
- .filter((session) => session.orderId !== null &&
7884
- session.symbol === symbol &&
7885
- session.isBuy === isBuy)
7886
- .map((session) => ({
7887
- orderId: session.orderId,
7888
- price: session.restingPrice,
7889
- size: session.size,
7890
- }));
8866
+ const ourOrders = [];
8867
+ for (const session of __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").values()) {
8868
+ if (session.orderId !== null &&
8869
+ session.symbol === symbol &&
8870
+ session.isBuy === isBuy) {
8871
+ ourOrders.push({
8872
+ orderId: session.orderId,
8873
+ price: session.restingPrice,
8874
+ size: session.size,
8875
+ });
8876
+ }
8877
+ }
7891
8878
  if (ourOrders.length === 0) {
7892
8879
  return new Map();
7893
8880
  }
@@ -7904,27 +8891,44 @@ async function _HyperLiquidProvider_resolveOwnRestingSizes(params) {
7904
8891
  const level = ownSide?.find((entry) => entry.px === order.price);
7905
8892
  return !level || parseFloat(level.sz) <= (recorded.get(order.price) ?? 0);
7906
8893
  });
7907
- let callerIsGone = false;
7908
- for (const order of ambiguous) {
7909
- try {
7910
- const live = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, order.orderId);
8894
+ let callerTerminalResult = null;
8895
+ let callerStatusUnavailable = false;
8896
+ const resolutions = await Promise.allSettled(ambiguous.map((order) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, order.orderId, CHASE_ORDER_STATUS_RETRY_COUNT)));
8897
+ resolutions.forEach((resolution, index) => {
8898
+ const order = ambiguous[index];
8899
+ if (resolution.status === 'fulfilled') {
8900
+ const { remainingSize: live, terminalStatus } = resolution.value;
7911
8901
  const delta = (live === null ? 0 : parseFloat(live)) - parseFloat(order.size);
7912
8902
  recorded.set(order.price, Math.max(0, (recorded.get(order.price) ?? 0) + delta));
7913
- if (live === null && order.orderId === callerOrderId) {
7914
- callerIsGone = true;
8903
+ if (terminalStatus !== null && order.orderId === callerOrderId) {
8904
+ callerTerminalResult = {
8905
+ remainingSize: live,
8906
+ status: terminalStatus,
8907
+ };
7915
8908
  }
7916
8909
  }
7917
- catch (error) {
7918
- // A failed lookup must not stop the chase. Keeping the recorded size can
7919
- // only over-subtract, which quotes a level deeper rather than leaving
7920
- // the order stranded at a stale price.
7921
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase own-size lookup failed', {
7922
- orderId: order.orderId,
7923
- error: ensureError(error, 'HyperLiquidProvider.chaseOwnSize').message,
7924
- });
8910
+ else {
8911
+ const { reason: error } = resolution;
8912
+ if (error instanceof ChaseOrderStatusUnavailableError &&
8913
+ order.orderId === callerOrderId) {
8914
+ callerStatusUnavailable = true;
8915
+ }
8916
+ else {
8917
+ // A failed lookup must not stop the chase. Keeping the recorded size
8918
+ // can only over-subtract, which quotes a level deeper rather than
8919
+ // leaving the order stranded at a stale price.
8920
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase own-size lookup failed', {
8921
+ orderId: order.orderId,
8922
+ error: ensureError(error, 'HyperLiquidProvider.chaseOwnSize')
8923
+ .message,
8924
+ });
8925
+ }
7925
8926
  }
8927
+ });
8928
+ if (callerStatusUnavailable) {
8929
+ return CHASE_ORDER_STATUS_UNAVAILABLE;
7926
8930
  }
7927
- return callerIsGone ? 'gone' : recorded;
8931
+ return callerTerminalResult ?? recorded;
7928
8932
  }, _HyperLiquidProvider_readOrderRemainder =
7929
8933
  /**
7930
8934
  * Read what a chase's just-cancelled order left unfilled.
@@ -7940,19 +8944,51 @@ async function _HyperLiquidProvider_resolveOwnRestingSizes(params) {
7940
8944
  * window in which a fill lands and is then re-placed on top.
7941
8945
  *
7942
8946
  * @param orderId - Exchange ID of the order that was just cancelled.
7943
- * @returns The unfilled size, or null when nothing is left to chase.
8947
+ * @param unknownStatusRetries - Remaining retries for a transient unknown ID.
8948
+ * @returns The unfilled size and exact terminal status, when present.
7944
8949
  */
7945
- async function _HyperLiquidProvider_readOrderRemainder(orderId) {
8950
+ async function _HyperLiquidProvider_readOrderRemainder(orderId, unknownStatusRetries = 0) {
7946
8951
  const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
7947
8952
  const status = await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient().orderStatus({
7948
8953
  user: userAddress,
7949
8954
  oid: parseInt(orderId, 10),
7950
8955
  });
7951
8956
  if (status.status !== 'order') {
7952
- return null;
8957
+ // unknownOid can be transient while the order-status index catches up.
8958
+ // Treating it as a fill would drop the only handle to a live order.
8959
+ if (unknownStatusRetries > 0) {
8960
+ await new Promise((resolve) => setTimeout(resolve, CHASE_ORDER_STATUS_RETRY_DELAY_MS));
8961
+ return await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, orderId, unknownStatusRetries - 1);
8962
+ }
8963
+ throw new ChaseOrderStatusUnavailableError();
7953
8964
  }
7954
8965
  const remaining = String(status.order.order.sz);
7955
- return parseFloat(remaining) > 0 ? remaining : null;
8966
+ const remainingSize = parseFloat(remaining) > 0 ? remaining : null;
8967
+ switch (status.order.status) {
8968
+ case 'open':
8969
+ case 'triggered':
8970
+ return { remainingSize, terminalStatus: null };
8971
+ case 'filled':
8972
+ return {
8973
+ remainingSize: null,
8974
+ terminalStatus: CHASE_ORDER_STATUS.Filled,
8975
+ };
8976
+ case 'canceled':
8977
+ return remainingSize === null
8978
+ ? {
8979
+ remainingSize: null,
8980
+ terminalStatus: CHASE_ORDER_STATUS.Filled,
8981
+ }
8982
+ : {
8983
+ remainingSize,
8984
+ terminalStatus: CHASE_ORDER_STATUS.Canceled,
8985
+ };
8986
+ default:
8987
+ return {
8988
+ remainingSize,
8989
+ terminalStatus: CHASE_ORDER_STATUS.Failed,
8990
+ };
8991
+ }
7956
8992
  }, _HyperLiquidProvider_cancelChaseChild =
7957
8993
  /**
7958
8994
  * Cancel the order a chase session currently has resting.
@@ -7965,7 +9001,7 @@ async function _HyperLiquidProvider_readOrderRemainder(orderId) {
7965
9001
  */
7966
9002
  async function _HyperLiquidProvider_cancelChaseChild(session, placingClient) {
7967
9003
  if (!session.orderId) {
7968
- return 'gone';
9004
+ return CancelChildOutcome.Gone;
7969
9005
  }
7970
9006
  // Looked up only once there is something to cancel, so a session with
7971
9007
  // nothing resting still answers on a provider whose client is already gone.
@@ -7979,7 +9015,7 @@ async function _HyperLiquidProvider_cancelChaseChild(session, placingClient) {
7979
9015
  catch (error) {
7980
9016
  const message = ensureError(error, 'HyperLiquidProvider.cancelChaseChild').message.toLowerCase();
7981
9017
  if (ALREADY_GONE_CANCEL_MARKERS.some((marker) => message.includes(marker))) {
7982
- return 'gone';
9018
+ return CancelChildOutcome.Gone;
7983
9019
  }
7984
9020
  throw error;
7985
9021
  }
@@ -8014,19 +9050,194 @@ async function _HyperLiquidProvider_retractOrphanedChaseOrder(session, placingCl
8014
9050
  error: ensureError(error, 'HyperLiquidProvider.startChaseSession')
8015
9051
  .message,
8016
9052
  });
8017
- return 'refused';
9053
+ return CancelChildOutcome.Refused;
8018
9054
  }
8019
9055
  }, _HyperLiquidProvider_stopChaseSession = function _HyperLiquidProvider_stopChaseSession(sessionId) {
8020
9056
  const session = __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").get(sessionId);
8021
9057
  if (!session) {
8022
9058
  return;
8023
9059
  }
9060
+ const wasActive = session.active;
8024
9061
  session.active = false;
8025
- session.status = CHASE_ORDER_STATUS.TerminationPending;
9062
+ if (wasActive) {
9063
+ session.status = CHASE_ORDER_STATUS.TerminationPending;
9064
+ }
8026
9065
  if (session.timer) {
8027
9066
  clearTimeout(session.timer);
8028
9067
  session.timer = null;
8029
9068
  }
9069
+ }, _HyperLiquidProvider_rebalanceChaseSession =
9070
+ /**
9071
+ * Rebalance one Chase session at most once at a time. Failed cleanup keeps
9072
+ * the transfer context so a later lifecycle read or cancel can retry it.
9073
+ *
9074
+ * @param session - Chase session whose manual HIP-3 transfer is complete.
9075
+ * @returns Whether no collateral cleanup remains for the session.
9076
+ */
9077
+ async function _HyperLiquidProvider_rebalanceChaseSession(session) {
9078
+ const { hip3Transfer } = session;
9079
+ if (!hip3Transfer) {
9080
+ return true;
9081
+ }
9082
+ if (session.orderId !== null ||
9083
+ session.replacingOrderId !== null ||
9084
+ session.pendingReplacement !== null) {
9085
+ return false;
9086
+ }
9087
+ let rebalancePromise = session.hip3RebalancePromise;
9088
+ if (!rebalancePromise) {
9089
+ rebalancePromise = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3PostOrderRebalance).call(this, hip3Transfer);
9090
+ session.hip3RebalancePromise = rebalancePromise;
9091
+ }
9092
+ try {
9093
+ const success = await rebalancePromise;
9094
+ if (success && session.hip3Transfer === hip3Transfer) {
9095
+ delete session.hip3Transfer;
9096
+ }
9097
+ return success;
9098
+ }
9099
+ finally {
9100
+ if (session.hip3RebalancePromise === rebalancePromise) {
9101
+ delete session.hip3RebalancePromise;
9102
+ }
9103
+ }
9104
+ }, _HyperLiquidProvider_adaptTwapOrder = function _HyperLiquidProvider_adaptTwapOrder(params) {
9105
+ const { historyEntry, now } = params;
9106
+ const { state, twapId } = historyEntry;
9107
+ if (twapId === undefined || !Number.isSafeInteger(twapId) || twapId < 0) {
9108
+ return null;
9109
+ }
9110
+ const totalSize = new BigNumber(state.sz);
9111
+ const executedSize = new BigNumber(state.executedSz);
9112
+ const executedNotional = new BigNumber(state.executedNtl);
9113
+ if (!totalSize.isFinite() ||
9114
+ !executedSize.isFinite() ||
9115
+ !executedNotional.isFinite() ||
9116
+ !Number.isFinite(state.timestamp) ||
9117
+ !Number.isFinite(historyEntry.time) ||
9118
+ !Number.isFinite(state.minutes)) {
9119
+ return null;
9120
+ }
9121
+ let boundedExecutedSize = executedSize;
9122
+ if (executedSize.isLessThan(0)) {
9123
+ boundedExecutedSize = new BigNumber(0);
9124
+ }
9125
+ else if (executedSize.isGreaterThan(totalSize)) {
9126
+ boundedExecutedSize = totalSize;
9127
+ }
9128
+ const rawRemainingSize = totalSize.minus(boundedExecutedSize);
9129
+ const remainingSize = rawRemainingSize.isGreaterThan(0)
9130
+ ? rawRemainingSize
9131
+ : new BigNumber(0);
9132
+ const status = resolveTwapOrderStatus(historyEntry, boundedExecutedSize, totalSize);
9133
+ const fills = params.sliceFills
9134
+ .map(adaptTwapOrderFill)
9135
+ .sort((left, right) => left.timestamp - right.timestamp);
9136
+ const startedAt = state.timestamp;
9137
+ const historyUpdatedAt = normalizeTwapHistoryTimestamp(historyEntry.time);
9138
+ const lastUpdated = Math.max(startedAt, historyUpdatedAt, ...fills.map((fill) => fill.timestamp));
9139
+ const durationMilliseconds = state.minutes * MILLISECONDS_PER_MINUTE;
9140
+ const elapsedTimeMilliseconds = Math.min(durationMilliseconds, Math.max(0, (status === PerpsTwapLifecycleStatus.Active ? now : lastUpdated) -
9141
+ startedAt));
9142
+ const fillProgressBps = totalSize.isGreaterThan(0)
9143
+ ? boundedExecutedSize
9144
+ .dividedBy(totalSize)
9145
+ .multipliedBy(BASIS_POINTS_DIVISOR)
9146
+ .integerValue(BigNumber.ROUND_FLOOR)
9147
+ .toNumber()
9148
+ : 0;
9149
+ const timeProgressBps = durationMilliseconds > 0
9150
+ ? Math.floor((elapsedTimeMilliseconds / durationMilliseconds) *
9151
+ BASIS_POINTS_DIVISOR)
9152
+ : 0;
9153
+ return {
9154
+ orderId: twapId.toString(),
9155
+ symbol: state.coin,
9156
+ side: state.side === 'B' ? 'buy' : 'sell',
9157
+ size: state.sz,
9158
+ executedSize: boundedExecutedSize.toString(),
9159
+ remainingSize: remainingSize.toFixed(),
9160
+ executedNotional: state.executedNtl,
9161
+ ...(executedSize.isGreaterThan(0) && {
9162
+ averagePrice: executedNotional.dividedBy(executedSize).toFixed(),
9163
+ }),
9164
+ fillProgressBps,
9165
+ timeProgressBps,
9166
+ elapsedTimeMilliseconds,
9167
+ durationMinutes: state.minutes,
9168
+ randomize: state.randomize,
9169
+ reduceOnly: state.reduceOnly,
9170
+ status,
9171
+ startedAt,
9172
+ lastUpdated,
9173
+ ...(historyEntry.status.status ===
9174
+ HyperLiquidTwapLifecycleStatus.Failed && {
9175
+ error: historyEntry.status.description,
9176
+ }),
9177
+ fills,
9178
+ };
9179
+ }, _HyperLiquidProvider_rebalanceTrackedTwapOrder =
9180
+ /**
9181
+ * Rebalance one tracked TWAP at most once at a time. A failed transfer keeps
9182
+ * the tracking entry so the next lifecycle read or cancel can retry it.
9183
+ *
9184
+ * @param trackingKey - Account-scoped TWAP tracking key.
9185
+ * @returns Whether no cleanup remains for the entry.
9186
+ */
9187
+ async function _HyperLiquidProvider_rebalanceTrackedTwapOrder(trackingKey) {
9188
+ const trackedOrder = __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey);
9189
+ if (!trackedOrder) {
9190
+ return true;
9191
+ }
9192
+ if (!trackedOrder.hip3Transfer) {
9193
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey) === trackedOrder) {
9194
+ __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").delete(trackingKey);
9195
+ }
9196
+ return true;
9197
+ }
9198
+ let { rebalancePromise } = trackedOrder;
9199
+ if (!rebalancePromise) {
9200
+ rebalancePromise = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3PostOrderRebalance).call(this, trackedOrder.hip3Transfer);
9201
+ trackedOrder.rebalancePromise = rebalancePromise;
9202
+ }
9203
+ try {
9204
+ const success = await rebalancePromise;
9205
+ if (success &&
9206
+ __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey) === trackedOrder) {
9207
+ __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").delete(trackingKey);
9208
+ }
9209
+ return success;
9210
+ }
9211
+ finally {
9212
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey) === trackedOrder &&
9213
+ trackedOrder.rebalancePromise === rebalancePromise) {
9214
+ delete trackedOrder.rebalancePromise;
9215
+ }
9216
+ }
9217
+ }, _HyperLiquidProvider_rebalanceTerminalHip3Twaps =
9218
+ /**
9219
+ * Return manually moved HIP-3 collateral after a TWAP reaches a terminal
9220
+ * venue state. The scoped registry survives reconnects so an active schedule
9221
+ * cannot lose its eventual cleanup path.
9222
+ *
9223
+ * @param orders - Current and historical TWAP lifecycle records.
9224
+ * @param scope - Current account and network identity.
9225
+ */
9226
+ async function _HyperLiquidProvider_rebalanceTerminalHip3Twaps(orders, scope) {
9227
+ for (const order of orders) {
9228
+ if (order.status === PerpsTwapLifecycleStatus.Active) {
9229
+ continue;
9230
+ }
9231
+ const trackingKey = getTwapOrderScopeKey({
9232
+ ...scope,
9233
+ orderId: order.orderId,
9234
+ });
9235
+ const trackedOrder = __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey);
9236
+ if (trackedOrder?.symbol !== order.symbol) {
9237
+ continue;
9238
+ }
9239
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceTrackedTwapOrder).call(this, trackingKey);
9240
+ }
8030
9241
  }, _HyperLiquidProvider_cancelStrategyOrder =
8031
9242
  /**
8032
9243
  * Cancel a strategy placement by its handle.
@@ -8073,21 +9284,64 @@ async function _HyperLiquidProvider_cancelTwapOrder(params) {
8073
9284
  if (!coinValidation.isValid) {
8074
9285
  throw new Error(coinValidation.error);
8075
9286
  }
8076
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
9287
+ const twapId = Number(params.orderId);
9288
+ if (!/^\d+$/u.test(params.orderId) || !Number.isSafeInteger(twapId)) {
9289
+ throw new Error(PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
9290
+ }
9291
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
9292
+ const network = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode() ? 'testnet' : 'mainnet';
9293
+ const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
9294
+ const trackingKey = getTwapOrderScopeKey({
9295
+ network,
9296
+ userAddress,
9297
+ orderId: params.orderId,
9298
+ });
9299
+ const trackedOrder = __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey);
9300
+ let ownershipAuthenticated = trackedOrder !== undefined;
9301
+ if (trackedOrder) {
9302
+ if (trackedOrder.symbol !== params.symbol) {
9303
+ throw new Error(PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
9304
+ }
9305
+ }
9306
+ else {
9307
+ let venueOrders = null;
9308
+ try {
9309
+ venueOrders = await this.getTwapOrders();
9310
+ }
9311
+ catch (error) {
9312
+ // History is an ownership check, not the cancellation transport. A
9313
+ // transient read failure must not make a live TWAP impossible to stop.
9314
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('TWAP history unavailable before cancellation', {
9315
+ orderId: params.orderId,
9316
+ error: ensureError(error, 'HyperLiquidProvider.cancelTwapOrderHistory').message,
9317
+ });
9318
+ }
9319
+ const venueOrder = venueOrders?.find((order) => order.orderId === params.orderId);
9320
+ if (venueOrders !== null && venueOrder?.symbol !== params.symbol) {
9321
+ throw new Error(PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
9322
+ }
9323
+ ownershipAuthenticated = venueOrder !== undefined;
9324
+ }
8077
9325
  const assetId = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
8078
9326
  symbol: params.symbol,
8079
9327
  dexName: parseAssetName(params.symbol).dex,
8080
9328
  });
8081
9329
  const result = await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient().twapCancel({
8082
9330
  a: assetId,
8083
- t: parseInt(params.orderId, 10),
9331
+ t: twapId,
8084
9332
  });
8085
9333
  const status = result.response?.data?.status;
8086
- if (status === 'success') {
9334
+ const cancelOutcome = classifyCancelStatus(status);
9335
+ if (cancelOutcome === CancelChildOutcome.Gone && !ownershipAuthenticated) {
9336
+ throw new Error(PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
9337
+ }
9338
+ if (cancelOutcome !== CancelChildOutcome.Refused) {
9339
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceTrackedTwapOrder).call(this, trackingKey);
8087
9340
  return { success: true, orderId: params.orderId };
8088
9341
  }
8089
- const rawError = status?.error ??
8090
- 'TWAP cancellation failed';
9342
+ const rawError = isStatusObject(status) && typeof status.error === 'string'
9343
+ ? status.error
9344
+ : 'TWAP cancellation failed';
8091
9345
  return createErrorResult(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mapError).call(this, new Error(rawError)), {
8092
9346
  success: false,
8093
9347
  orderId: params.orderId,
@@ -8104,19 +9358,25 @@ async function _HyperLiquidProvider_cancelScaleOrder(params) {
8104
9358
  if (!group) {
8105
9359
  throw new Error(PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
8106
9360
  }
8107
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
8108
- const result = await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient().cancel({
8109
- cancels: group.orderIds.map((orderId) => ({
8110
- a: group.assetId,
8111
- o: parseInt(orderId, 10),
8112
- })),
9361
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
9362
+ const assetId = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
9363
+ symbol: group.symbol,
9364
+ dexName: parseAssetName(group.symbol).dex,
8113
9365
  });
8114
- const statuses = result.response?.data?.statuses ?? [];
8115
- // A rung that filled or was cancelled individually comes back as a
8116
- // rejection, but nothing of it is resting — counting it as still live would
8117
- // pin the handle open for a ladder that is entirely off the book.
8118
- const remaining = group.orderIds.filter((_unused, index) => classifyCancelStatus(statuses[index]) === 'refused');
9366
+ const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
9367
+ const cancelRequests = group.orderIds.map((orderId) => ({
9368
+ a: assetId,
9369
+ o: Number(orderId),
9370
+ }));
9371
+ const remaining = (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequests).call(this, exchangeClient, cancelRequests)).map(String);
9372
+ /*
9373
+ * A rung that filled or was cancelled individually comes back as a
9374
+ * rejection, but nothing of it is resting. The shared cancellation helper
9375
+ * distinguishes that result from a refusal while retaining every child
9376
+ * after a malformed or non-ok batch response.
9377
+ */
8119
9378
  if (remaining.length === 0) {
9379
+ __classPrivateFieldGet(this, _HyperLiquidProvider_cancelledScaleOrderGroups, "f").add(params.orderId);
8120
9380
  __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").delete(params.orderId);
8121
9381
  return { success: true, orderId: params.orderId };
8122
9382
  }
@@ -8124,7 +9384,7 @@ async function _HyperLiquidProvider_cancelScaleOrder(params) {
8124
9384
  // handle registered against what is left means the caller can retry the
8125
9385
  // same cancel rather than being told the ladder is gone when it is not.
8126
9386
  __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").set(params.orderId, {
8127
- assetId: group.assetId,
9387
+ symbol: group.symbol,
8128
9388
  orderIds: remaining,
8129
9389
  });
8130
9390
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Scale group cancel left children resting', {
@@ -8185,39 +9445,120 @@ async function _HyperLiquidProvider_performChaseTermination(params) {
8185
9445
  // Nothing rests: the order filled, something else cancelled it, or a
8186
9446
  // replacement failed to go up. Either way the session ends cleanly.
8187
9447
  if (session.orderId === null) {
8188
- __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").delete(params.orderId);
9448
+ session.removeAfterRebalance = true;
9449
+ if (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session)) {
9450
+ __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").delete(params.orderId);
9451
+ }
8189
9452
  return { success: true, orderId: params.orderId };
8190
9453
  }
8191
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
9454
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
8192
9455
  // Only a refusal leaves an order behind. A child that had already filled or
8193
9456
  // been cancelled is reported as a rejection too, but nothing of it is
8194
9457
  // resting — treating that as a failure would pin the handle open forever on
8195
9458
  // a chase that is entirely finished.
8196
9459
  const cancelOutcome = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelChaseChild).call(this, session);
8197
- if (cancelOutcome === 'refused') {
9460
+ if (cancelOutcome === CancelChildOutcome.Refused) {
8198
9461
  // The order is still resting. The session stays registered — stopped, but
8199
9462
  // still cancellable — so the caller can retry with the same handle.
8200
9463
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase cancel left its order resting', {
8201
9464
  sessionId: params.orderId,
8202
9465
  orderId: session.orderId,
8203
9466
  });
9467
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
8204
9468
  return createErrorResult(new Error(PERPS_ERROR_CODES.ORDER_STRATEGY_CANCEL_INCOMPLETE), { success: false, orderId: params.orderId });
8205
9469
  }
8206
- __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").delete(params.orderId);
9470
+ session.orderId = null;
9471
+ session.status = CHASE_ORDER_STATUS.Canceled;
9472
+ session.removeAfterRebalance = true;
9473
+ if (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session)) {
9474
+ __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").delete(params.orderId);
9475
+ }
8207
9476
  return { success: true, orderId: params.orderId };
8208
- }, _HyperLiquidProvider_readOrderIdFromStatus = function _HyperLiquidProvider_readOrderIdFromStatus(status) {
9477
+ }, _HyperLiquidProvider_cancelOrderRequests =
9478
+ /**
9479
+ * Cancel a batch and retain every order that may still be live.
9480
+ *
9481
+ * A malformed, truncated, or rejected response cannot prove which requests
9482
+ * reached the venue, so every requested ID remains recoverable for a retry.
9483
+ *
9484
+ * @param exchangeClient - Client that owns the orders.
9485
+ * @param requests - Venue cancel requests.
9486
+ * @returns Order IDs that may still be resting.
9487
+ */
9488
+ async function _HyperLiquidProvider_cancelOrderRequests(exchangeClient, requests) {
9489
+ return (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequestBatch).call(this, exchangeClient, requests))
9490
+ .remainingOrderIds;
9491
+ }, _HyperLiquidProvider_cancelOrderRequestBatch =
9492
+ /**
9493
+ * Cancel a batch while distinguishing confirmed cancellations from orders
9494
+ * that were already gone. Replacement rollback may only restore the former.
9495
+ *
9496
+ * @param exchangeClient - Client that owns the orders.
9497
+ * @param requests - Venue cancel requests.
9498
+ * @returns IDs that may still rest and IDs this call confirmed it cancelled.
9499
+ */
9500
+ async function _HyperLiquidProvider_cancelOrderRequestBatch(exchangeClient, requests) {
9501
+ if (requests.length === 0) {
9502
+ return {
9503
+ remainingOrderIds: [],
9504
+ cancelledOrderIds: [],
9505
+ responseComplete: true,
9506
+ };
9507
+ }
9508
+ try {
9509
+ const result = await exchangeClient.cancel({ cancels: requests });
9510
+ const statuses = result.response?.data?.statuses ?? [];
9511
+ if (result.status !== 'ok' || statuses.length !== requests.length) {
9512
+ return {
9513
+ remainingOrderIds: requests.map((request) => request.o),
9514
+ cancelledOrderIds: [],
9515
+ responseComplete: false,
9516
+ };
9517
+ }
9518
+ const remainingOrderIds = [];
9519
+ const cancelledOrderIds = [];
9520
+ requests.forEach((request, index) => {
9521
+ const outcome = classifyCancelStatus(statuses[index]);
9522
+ if (outcome === CancelChildOutcome.Refused) {
9523
+ remainingOrderIds.push(request.o);
9524
+ }
9525
+ else if (outcome === CancelChildOutcome.Cancelled) {
9526
+ cancelledOrderIds.push(request.o);
9527
+ }
9528
+ });
9529
+ return { remainingOrderIds, cancelledOrderIds, responseComplete: true };
9530
+ }
9531
+ catch (error) {
9532
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Order cancellation batch failed', {
9533
+ error: ensureError(error, 'HyperLiquidProvider.cancelOrderRequests')
9534
+ .message,
9535
+ orderIds: requests.map((request) => request.o),
9536
+ });
9537
+ return {
9538
+ remainingOrderIds: requests.map((request) => request.o),
9539
+ cancelledOrderIds: [],
9540
+ responseComplete: false,
9541
+ };
9542
+ }
9543
+ }, _HyperLiquidProvider_readOrderPlacementOutcome = function _HyperLiquidProvider_readOrderPlacementOutcome(status) {
8209
9544
  if (!isStatusObject(status)) {
8210
9545
  return undefined;
8211
9546
  }
8212
- // `hasProperty` types the property as `unknown`, so each branch is cast to
8213
- // the only shape the exchange puts there.
8214
- const restingOrder = hasProperty(status, 'resting')
8215
- ? status.resting
8216
- : undefined;
8217
- const filledOrder = hasProperty(status, 'filled')
8218
- ? status.filled
8219
- : undefined;
8220
- return (restingOrder?.oid?.toString() ?? filledOrder?.oid?.toString() ?? undefined);
9547
+ for (const state of ['resting', 'filled']) {
9548
+ if (!hasProperty(status, state)) {
9549
+ continue;
9550
+ }
9551
+ const order = status[state];
9552
+ if (isStatusObject(order) &&
9553
+ typeof order.oid === 'number' &&
9554
+ Number.isSafeInteger(order.oid) &&
9555
+ order.oid >= 0) {
9556
+ return { orderId: order.oid.toString(), state };
9557
+ }
9558
+ }
9559
+ return undefined;
9560
+ }, _HyperLiquidProvider_readOrderIdFromStatus = function _HyperLiquidProvider_readOrderIdFromStatus(status) {
9561
+ return __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderPlacementOutcome).call(this, status)?.orderId;
8221
9562
  }, _HyperLiquidProvider_getDiscountedBuilderFee = function _HyperLiquidProvider_getDiscountedBuilderFee() {
8222
9563
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") === undefined) {
8223
9564
  return BUILDER_FEE_CONFIG.MaxFeeTenthsBps;
@@ -8232,15 +9573,14 @@ async function _HyperLiquidProvider_performChaseTermination(params) {
8232
9573
  * cached for this provider/account session. Until then, the ordinary builder
8233
9574
  * and standard fee keep the trade attributable and non-blocking.
8234
9575
  *
9576
+ * @param setupContext - Account, network, and builder approved for the order.
8235
9577
  * @returns HyperLiquid builder address and fee payload.
8236
9578
  */
8237
- async function _HyperLiquidProvider_getBuilderOrderContext() {
8238
- const isTestnet = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode();
8239
- const network = isTestnet ? 'testnet' : 'mainnet';
8240
- const defaultBuilder = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderAddress).call(this, isTestnet);
9579
+ async function _HyperLiquidProvider_getBuilderOrderContext(setupContext) {
9580
+ const { network, userAddress, builderAddress: defaultBuilder, } = setupContext;
9581
+ const isTestnet = network === 'testnet';
8241
9582
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_userFeeResolution, "f")?.source === 'subscription') {
8242
9583
  const subscriptionBuilder = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getSubscriptionBuilderAddress).call(this, isTestnet);
8243
- const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
8244
9584
  if (subscriptionBuilder &&
8245
9585
  __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").has(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getApprovedBuilderKey).call(this, network, userAddress, subscriptionBuilder))) {
8246
9586
  return { b: subscriptionBuilder, f: 0 };
@@ -8390,6 +9730,41 @@ async function _HyperLiquidProvider_queryDexPositions(dexName) {
8390
9730
  });
8391
9731
  return { answered: false, positions: [] };
8392
9732
  }
9733
+ }, _HyperLiquidProvider_restoreScaleOrderGroups = function _HyperLiquidProvider_restoreScaleOrderGroups(orders) {
9734
+ const recovered = new Map();
9735
+ for (const order of orders) {
9736
+ if (!order.strategyGroupId) {
9737
+ continue;
9738
+ }
9739
+ const group = recovered.get(order.strategyGroupId) ?? {
9740
+ symbol: order.symbol,
9741
+ orderIds: [],
9742
+ };
9743
+ group.orderIds.push(order.orderId);
9744
+ recovered.set(order.strategyGroupId, group);
9745
+ }
9746
+ for (const [groupId, group] of recovered) {
9747
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_cancelledScaleOrderGroups, "f").has(groupId)) {
9748
+ continue;
9749
+ }
9750
+ const existingGroup = __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").get(groupId);
9751
+ if (!existingGroup) {
9752
+ __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").set(groupId, group);
9753
+ continue;
9754
+ }
9755
+ if (existingGroup.symbol === group.symbol) {
9756
+ existingGroup.orderIds = [
9757
+ ...new Set([...existingGroup.orderIds, ...group.orderIds]),
9758
+ ];
9759
+ }
9760
+ else {
9761
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Scale group symbol mismatch during recovery', {
9762
+ groupId,
9763
+ existingSymbol: existingGroup.symbol,
9764
+ recoveredSymbol: group.symbol,
9765
+ });
9766
+ }
9767
+ }
8393
9768
  }, _HyperLiquidProvider_getAllMids =
8394
9769
  /**
8395
9770
  * Get allMids for a DEX — uses WS snapshot as primary source, REST as fallback.
@@ -8406,7 +9781,7 @@ async function _HyperLiquidProvider_getAllMids(infoClient, dexParam) {
8406
9781
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getMarketDataWithPrices] No WS allMids snapshot, falling back to REST', { dexParam: dexParam ?? 'main' });
8407
9782
  const mids = await infoClient.allMids(dexParam ? { dex: dexParam } : undefined);
8408
9783
  return mids ?? {};
8409
- }, _HyperLiquidProvider_fetchSingleDexFresh = async function _HyperLiquidProvider_fetchSingleDexFresh(infoClient, dex) {
9784
+ }, _HyperLiquidProvider_fetchSingleDexFresh = async function _HyperLiquidProvider_fetchSingleDexFresh(infoClient, dex, lifecycleGeneration) {
8410
9785
  const dexParam = dex ?? '';
8411
9786
  let metaAndCtxs = null;
8412
9787
  try {
@@ -8434,10 +9809,12 @@ async function _HyperLiquidProvider_getAllMids(infoClient, dexParam) {
8434
9809
  allMidsErrorMessage = ensureError(error, 'HyperLiquidProvider.getMarketDataWithPrices.allMids').message;
8435
9810
  }
8436
9811
  if (meta?.universe) {
8437
- __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").set(dexParam, meta);
8438
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexMetaCache(dexParam, meta);
8439
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexAssetCtxsCache(dexParam, assetCtxs);
8440
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dex, meta);
9812
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheDexMetadataFromRead).call(this, {
9813
+ dex,
9814
+ meta,
9815
+ assetCtxs,
9816
+ lifecycleGeneration,
9817
+ });
8441
9818
  }
8442
9819
  return {
8443
9820
  dex,
@@ -8540,6 +9917,128 @@ async function _HyperLiquidProvider_excludeNonUsdcCollateralResults(results) {
8540
9917
  return false;
8541
9918
  }
8542
9919
  return Date.now() - cached.timestamp < cached.ttl;
9920
+ }, _HyperLiquidProvider_performDisconnect =
9921
+ /**
9922
+ * Perform one provider teardown shared by every concurrent caller.
9923
+ *
9924
+ * @returns A promise that resolves to the result.
9925
+ */
9926
+ async function _HyperLiquidProvider_performDisconnect() {
9927
+ __classPrivateFieldSet(this, _HyperLiquidProvider_chasePlacementBlockers, __classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") + 1, "f");
9928
+ __classPrivateFieldSet(this, _HyperLiquidProvider_strategyGeneration, __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f") + 1, "f");
9929
+ __classPrivateFieldSet(this, _HyperLiquidProvider_disconnectOperationsInFlight, __classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") + 1, "f");
9930
+ __classPrivateFieldSet(this, _HyperLiquidProvider_isDisconnected, true, "f");
9931
+ __classPrivateFieldSet(this, _HyperLiquidProvider_lifecycleGeneration, __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f") + 1, "f");
9932
+ try {
9933
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Disconnecting provider', {
9934
+ isTestnet: __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode(),
9935
+ timestamp: new Date().toISOString(),
9936
+ });
9937
+ // Clear subscriptions through subscription service
9938
+ __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").clearAll();
9939
+ // Stop every chase loop: each holds a pending timer, and re-pricing after
9940
+ // a disconnect would sign orders against a client that is being torn down.
9941
+ // Orders already resting are deliberately left alone — disconnecting is
9942
+ // not a request to cancel the user's positions or orders.
9943
+ // Invalidate placements first, then drain every mutation before clearing
9944
+ // the registry and tearing down the clients they use.
9945
+ for (const [sessionId, session] of __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").entries()) {
9946
+ if (session.active) {
9947
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_stopChaseSession).call(this, sessionId);
9948
+ }
9949
+ }
9950
+ await Promise.all([...__classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementWaiters, "f")]);
9951
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f");
9952
+ await Promise.allSettled([...__classPrivateFieldGet(this, _HyperLiquidProvider_chaseTerminations, "f").values()]);
9953
+ await Promise.all([...__classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").values()].map(async (session) => await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session)));
9954
+ __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").clear();
9955
+ __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTerminations, "f").clear();
9956
+ __classPrivateFieldGet(this, _HyperLiquidProvider_terminatedChaseHandles, "f").clear();
9957
+ __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").clear();
9958
+ __classPrivateFieldGet(this, _HyperLiquidProvider_cancelledScaleOrderGroups, "f").clear();
9959
+ // Clear session caches. Capability and fee reads use the lifecycle
9960
+ // generation above to discard any old response that resolves later.
9961
+ this.clearFeeCache();
9962
+ __classPrivateFieldGet(this, _HyperLiquidProvider_referralCheckCache, "f").clear();
9963
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").clear();
9964
+ __classPrivateFieldSet(this, _HyperLiquidProvider_subscriptionBuilderApprovalEpoch, __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionBuilderApprovalEpoch, "f") + 1, "f");
9965
+ __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").clear();
9966
+ __classPrivateFieldSet(this, _HyperLiquidProvider_userFeeResolution, undefined, "f");
9967
+ __classPrivateFieldSet(this, _HyperLiquidProvider_userFeeDiscountBips, undefined, "f");
9968
+ // UnifiedAccountCache stays global to avoid repeated signing requests.
9969
+ __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").clear();
9970
+ __classPrivateFieldSet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, null, "f");
9971
+ __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesMarketsByDex, "f").clear();
9972
+ __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesRefreshByDex, "f").clear();
9973
+ __classPrivateFieldSet(this, _HyperLiquidProvider_cachedSpotMeta, null, "f");
9974
+ __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").reset();
9975
+ __classPrivateFieldSet(this, _HyperLiquidProvider_dexDiscoveryComplete, false, "f");
9976
+ // Await pending initialization before clearing to prevent the IIFE from
9977
+ // setting clientsInitialized = true after disconnect completes
9978
+ const pendingInit = __classPrivateFieldGet(this, _HyperLiquidProvider_initializationPromise, "f");
9979
+ const pendingReady = __classPrivateFieldGet(this, _HyperLiquidProvider_ensureReadyPromise, "f");
9980
+ const pendingTradingSetup = __classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f");
9981
+ const pendingBuilderFeeSetups = [
9982
+ ...__classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").values(),
9983
+ ];
9984
+ // Clear references first to prevent new callers from reusing
9985
+ __classPrivateFieldSet(this, _HyperLiquidProvider_initializationPromise, null, "f");
9986
+ __classPrivateFieldSet(this, _HyperLiquidProvider_ensureReadyPromise, null, "f");
9987
+ __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, null, "f");
9988
+ __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupComplete, false, "f");
9989
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").clear();
9990
+ __classPrivateFieldGet(this, _HyperLiquidProvider_pendingBuilderFeeApprovals, "f").clear();
9991
+ // Wait for pending operations to complete (ignore errors)
9992
+ // This prevents IIFEs from setting state after disconnect completes
9993
+ if (pendingInit) {
9994
+ try {
9995
+ await pendingInit;
9996
+ }
9997
+ catch {
9998
+ // Ignore - we're disconnecting anyway
9999
+ }
10000
+ }
10001
+ if (pendingReady) {
10002
+ try {
10003
+ await pendingReady;
10004
+ }
10005
+ catch {
10006
+ // Ignore - we're disconnecting anyway
10007
+ }
10008
+ }
10009
+ if (pendingTradingSetup) {
10010
+ try {
10011
+ await pendingTradingSetup;
10012
+ }
10013
+ catch {
10014
+ // Ignore - we're disconnecting anyway
10015
+ }
10016
+ }
10017
+ for (const pendingBuilderFeeSetup of pendingBuilderFeeSetups) {
10018
+ try {
10019
+ await pendingBuilderFeeSetup;
10020
+ }
10021
+ catch {
10022
+ // Ignore - we're disconnecting anyway
10023
+ }
10024
+ }
10025
+ // Reset client initialization flag so wallet adapter will be recreated with new account
10026
+ // This fixes account synchronization issue where old account's address persists in wallet adapter
10027
+ __classPrivateFieldSet(this, _HyperLiquidProvider_clientsInitialized, false, "f");
10028
+ // Disconnect client service
10029
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").disconnect();
10030
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Provider fully disconnected', {
10031
+ timestamp: new Date().toISOString(),
10032
+ });
10033
+ return { success: true };
10034
+ }
10035
+ catch (error) {
10036
+ return createErrorResult(error, { success: false });
10037
+ }
10038
+ finally {
10039
+ __classPrivateFieldSet(this, _HyperLiquidProvider_chasePlacementBlockers, __classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") - 1, "f");
10040
+ __classPrivateFieldSet(this, _HyperLiquidProvider_disconnectOperationsInFlight, __classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") - 1, "f");
10041
+ }
8543
10042
  }, _HyperLiquidProvider_getBuilderAddress = function _HyperLiquidProvider_getBuilderAddress(isTestnet) {
8544
10043
  // || intentional: env vars default to '' which must fall through to the hardcoded default
8545
10044
  if (isTestnet) {
@@ -8793,4 +10292,9 @@ async function _HyperLiquidProvider_setReferralCode() {
8793
10292
  throw error;
8794
10293
  }
8795
10294
  };
10295
+ // Native TWAP IDs and any manual HIP-3 collateral transfer must outlive one
10296
+ // provider instance: controller disconnect/reinitialization replaces the
10297
+ // provider while the venue schedule keeps running. Each controller messenger
10298
+ // owns one registry, so separate controller lifecycles never share handles.
10299
+ _HyperLiquidProvider_trackedTwapOrdersByMessenger = { value: new WeakMap() };
8796
10300
  //# sourceMappingURL=HyperLiquidProvider.mjs.map