@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
@@ -10,10 +10,11 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  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");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- 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
+ 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;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.HyperLiquidProvider = void 0;
16
16
  const utils_1 = require("@metamask/utils");
17
+ const bignumber_js_1 = require("bignumber.js");
17
18
  const uuid_1 = require("uuid");
18
19
  const eventNames_js_1 = require("../constants/eventNames.cjs");
19
20
  const hyperLiquidConfig_js_1 = require("../constants/hyperLiquidConfig.cjs");
@@ -28,6 +29,7 @@ const TradingReadinessCache_js_1 = require("../services/TradingReadinessCache.cj
28
29
  const hyperliquid_types_js_1 = require("../types/hyperliquid-types.cjs");
29
30
  const index_js_1 = require("../types/index.cjs");
30
31
  const accountUtils_js_1 = require("../utils/accountUtils.cjs");
32
+ const capabilitySymbols_js_1 = require("../utils/capabilitySymbols.cjs");
31
33
  const errorUtils_js_1 = require("../utils/errorUtils.cjs");
32
34
  const hyperLiquidAbstraction_js_1 = require("../utils/hyperLiquidAbstraction.cjs");
33
35
  const hyperLiquidAdapter_js_1 = require("../utils/hyperLiquidAdapter.cjs");
@@ -38,6 +40,7 @@ const marketUtils_js_1 = require("../utils/marketUtils.cjs");
38
40
  const orderCalculations_js_1 = require("../utils/orderCalculations.cjs");
39
41
  const orderTypes_js_1 = require("../utils/orderTypes.cjs");
40
42
  const standaloneInfoClient_js_1 = require("../utils/standaloneInfoClient.cjs");
43
+ const stringParseUtils_js_1 = require("../utils/stringParseUtils.cjs");
41
44
  // getStreamManagerInstance removed: use this.#deps.streamManager instead
42
45
  /**
43
46
  * Type guard to check if a status is an object (not a string literal like "waitingForFill")
@@ -62,15 +65,54 @@ const ALREADY_GONE_CANCEL_MARKERS = [
62
65
  'already canceled',
63
66
  'already cancelled',
64
67
  'order not found',
68
+ 'twap not found',
65
69
  ];
66
70
  const RETRYABLE_CHASE_PLACEMENT_MARKERS = [
67
71
  'post only order would have immediately matched',
68
72
  'price too far from oracle',
69
73
  ];
74
+ const CHASE_ORDER_STATUS_RETRY_COUNT = 2;
75
+ const CHASE_ORDER_STATUS_RETRY_DELAY_MS = 100;
76
+ const CHASE_ORDER_STATUS_UNAVAILABLE = 'status_unavailable';
77
+ class ChaseOrderStatusUnavailableError extends Error {
78
+ constructor() {
79
+ super('Chase order status unavailable');
80
+ this.name = 'ChaseOrderStatusUnavailableError';
81
+ }
82
+ }
70
83
  const isRetryableChasePlacementError = (error) => {
71
84
  const message = (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.startChaseSession').message.toLowerCase();
72
85
  return RETRYABLE_CHASE_PLACEMENT_MARKERS.some((marker) => message.includes(marker));
73
86
  };
87
+ /**
88
+ * What happened to one order a cancel was asked to remove.
89
+ *
90
+ * `Cancelled` and `Gone` both mean nothing of it is resting, which is all a
91
+ * caller asked for; only `Refused` leaves an order behind.
92
+ */
93
+ var CancelChildOutcome;
94
+ (function (CancelChildOutcome) {
95
+ CancelChildOutcome["Cancelled"] = "cancelled";
96
+ CancelChildOutcome["Gone"] = "gone";
97
+ CancelChildOutcome["Refused"] = "refused";
98
+ })(CancelChildOutcome || (CancelChildOutcome = {}));
99
+ var HyperLiquidTwapLifecycleStatus;
100
+ (function (HyperLiquidTwapLifecycleStatus) {
101
+ HyperLiquidTwapLifecycleStatus["Activated"] = "activated";
102
+ HyperLiquidTwapLifecycleStatus["Failed"] = "error";
103
+ HyperLiquidTwapLifecycleStatus["Finished"] = "finished";
104
+ HyperLiquidTwapLifecycleStatus["Stopped"] = "stopped";
105
+ HyperLiquidTwapLifecycleStatus["Terminated"] = "terminated";
106
+ HyperLiquidTwapLifecycleStatus["WaitingForTrigger"] = "waitingForTrigger";
107
+ })(HyperLiquidTwapLifecycleStatus || (HyperLiquidTwapLifecycleStatus = {}));
108
+ var PerpsTwapLifecycleStatus;
109
+ (function (PerpsTwapLifecycleStatus) {
110
+ PerpsTwapLifecycleStatus["Active"] = "active";
111
+ PerpsTwapLifecycleStatus["Canceled"] = "canceled";
112
+ PerpsTwapLifecycleStatus["Completed"] = "completed";
113
+ PerpsTwapLifecycleStatus["CompletedUnderfilled"] = "completed_underfilled";
114
+ PerpsTwapLifecycleStatus["Failed"] = "failed";
115
+ })(PerpsTwapLifecycleStatus || (PerpsTwapLifecycleStatus = {}));
74
116
  /**
75
117
  * Classify one entry of a cancel response.
76
118
  *
@@ -79,14 +121,14 @@ const isRetryableChasePlacementError = (error) => {
79
121
  */
80
122
  const classifyCancelStatus = (status) => {
81
123
  if (status === 'success') {
82
- return 'cancelled';
124
+ return CancelChildOutcome.Cancelled;
83
125
  }
84
126
  const message = (isStatusObject(status) && typeof status.error === 'string'
85
127
  ? status.error
86
128
  : '').toLowerCase();
87
129
  return ALREADY_GONE_CANCEL_MARKERS.some((marker) => message.includes(marker))
88
- ? 'gone'
89
- : 'refused';
130
+ ? CancelChildOutcome.Gone
131
+ : CancelChildOutcome.Refused;
90
132
  };
91
133
  /**
92
134
  * Read the best price on one side of the book, discounting the chase's own
@@ -188,6 +230,29 @@ const validateStrategyNotional = (params) => {
188
230
  }
189
231
  return { isValid: true };
190
232
  };
233
+ /**
234
+ * Create one recoverable identity for a Scale ladder and one venue CLOID per
235
+ * rung. HyperLiquid requires every CLOID to be unique, so the last byte holds
236
+ * the rung index while the preceding 15 bytes identify the shared group.
237
+ *
238
+ * @param count - Number of ladder rungs.
239
+ * @returns The public group handle and venue client order IDs.
240
+ */
241
+ const createScaleOrderIdentity = (count) => {
242
+ const groupKey = `${hyperLiquidAdapter_js_1.HYPERLIQUID_SCALE_CLOID_MARKER}${(0, uuid_1.v4)()
243
+ .replace(/-/gu, '')
244
+ .slice(0, 22)}`;
245
+ const clientOrderIds = Array.from({ length: count }, (_, index) => {
246
+ const clientOrderId = `0x${groupKey}${index
247
+ .toString(16)
248
+ .padStart(2, '0')}`;
249
+ if (!(0, utils_1.isHexString)(clientOrderId)) {
250
+ throw new Error('Failed to create Scale client order ID');
251
+ }
252
+ return clientOrderId;
253
+ });
254
+ return { groupId: `scale:${groupKey}`, clientOrderIds };
255
+ };
191
256
  /**
192
257
  * Collect the order IDs of every TP/SL child carried by a parent order.
193
258
  *
@@ -299,6 +364,76 @@ function collectPositionTriggerOrders(params) {
299
364
  ...(stopLossSummaryPrice && { stopLossPrice: stopLossSummaryPrice }),
300
365
  };
301
366
  }
367
+ const MAX_API_FEE_RATE = 1;
368
+ const MAX_API_FEE_DISCOUNT = 1;
369
+ /**
370
+ * Fee applicability by canonical order type.
371
+ *
372
+ * The configuration type makes this map exhaustive, so a new order type cannot
373
+ * inherit a builder-fee policy accidentally. HyperLiquid's dedicated TWAP
374
+ * action has no builder field. Every other standalone placement uses an order
375
+ * action that can carry one builder context. This policy is deliberately not a
376
+ * constructor or order input. A future backend policy must be resolved inside
377
+ * the provider from its trusted source rather than accepted from a client.
378
+ */
379
+ const HYPERLIQUID_ORDER_FEE_CONFIG = {
380
+ market: { chargesMetamaskBuilderFee: true },
381
+ limit: { chargesMetamaskBuilderFee: true },
382
+ stop_market: { chargesMetamaskBuilderFee: true },
383
+ stop_limit: { chargesMetamaskBuilderFee: true },
384
+ take_profit_market: { chargesMetamaskBuilderFee: true },
385
+ take_profit_limit: { chargesMetamaskBuilderFee: true },
386
+ twap: { chargesMetamaskBuilderFee: false },
387
+ scale: { chargesMetamaskBuilderFee: true },
388
+ chase: { chargesMetamaskBuilderFee: true },
389
+ };
390
+ const MILLISECONDS_PER_MINUTE = 60000;
391
+ const MILLISECONDS_PER_SECOND = 1000;
392
+ /**
393
+ * Normalize the TWAP history timestamp documented in seconds while tolerating
394
+ * an already-millisecond value from a future SDK response.
395
+ *
396
+ * @param timestamp - Venue timestamp in seconds or milliseconds.
397
+ * @returns Timestamp in milliseconds.
398
+ */
399
+ const normalizeTwapHistoryTimestamp = (timestamp) => timestamp < 1000000000000
400
+ ? timestamp * MILLISECONDS_PER_SECOND
401
+ : timestamp;
402
+ const getTwapOrderScopeKey = (params) => `${params.network}:${params.userAddress.toLowerCase()}:${params.orderId}`;
403
+ const resolveTwapOrderStatus = (historyEntry, executedSize, totalSize) => {
404
+ switch (historyEntry.status.status) {
405
+ case HyperLiquidTwapLifecycleStatus.Activated:
406
+ case HyperLiquidTwapLifecycleStatus.WaitingForTrigger:
407
+ return PerpsTwapLifecycleStatus.Active;
408
+ case HyperLiquidTwapLifecycleStatus.Finished:
409
+ return executedSize.isLessThan(totalSize)
410
+ ? PerpsTwapLifecycleStatus.CompletedUnderfilled
411
+ : PerpsTwapLifecycleStatus.Completed;
412
+ case HyperLiquidTwapLifecycleStatus.Terminated:
413
+ case HyperLiquidTwapLifecycleStatus.Stopped:
414
+ return PerpsTwapLifecycleStatus.Canceled;
415
+ case HyperLiquidTwapLifecycleStatus.Failed:
416
+ return PerpsTwapLifecycleStatus.Failed;
417
+ default:
418
+ // A new venue status is not proof that a schedule stopped. Keep it
419
+ // non-terminal so collateral cannot be reclaimed from a live TWAP.
420
+ return PerpsTwapLifecycleStatus.Active;
421
+ }
422
+ };
423
+ const adaptTwapOrderFill = (entry) => ({
424
+ fillId: entry.fill.tid.toString(),
425
+ orderId: entry.fill.oid.toString(),
426
+ side: entry.fill.side === 'B' ? 'buy' : 'sell',
427
+ price: entry.fill.px,
428
+ size: entry.fill.sz,
429
+ fee: entry.fill.fee,
430
+ feeToken: entry.fill.feeToken,
431
+ ...(entry.fill.builderFee === undefined
432
+ ? {}
433
+ : { builderFee: entry.fill.builderFee }),
434
+ timestamp: entry.fill.time,
435
+ transactionHash: entry.fill.hash,
436
+ });
302
437
  /**
303
438
  * HyperLiquid provider implementation
304
439
  *
@@ -313,7 +448,7 @@ function collectPositionTriggerOrders(params) {
313
448
  class HyperLiquidProvider {
314
449
  constructor(options) {
315
450
  _HyperLiquidProvider_instances.add(this);
316
- this.protocolId = 'hyperliquid';
451
+ this.protocolId = perpsConfig_js_1.PROVIDER_CONFIG.DefaultProvider;
317
452
  // Platform dependencies for logging and debugging
318
453
  _HyperLiquidProvider_deps.set(this, void 0);
319
454
  // Service instances
@@ -329,6 +464,16 @@ class HyperLiquidProvider {
329
464
  // Cache for raw meta responses (shared across methods to avoid redundant API calls)
330
465
  // Filtering is applied on-demand (cheap array operations) - no need for separate processed cache
331
466
  _HyperLiquidProvider_cachedMetaByDex.set(this, new Map());
467
+ // Fresh filtered markets used only for capability discovery. Each routed
468
+ // DEX owns its freshness window and coalesced refresh.
469
+ _HyperLiquidProvider_orderCapabilitiesMarketsByDex.set(this, new Map());
470
+ _HyperLiquidProvider_orderCapabilitiesRefreshByDex.set(this, new Map());
471
+ // Sticky for this instance. PerpsController discards disconnected providers
472
+ // during initialization; direct callers must call initialize() before reuse.
473
+ _HyperLiquidProvider_isDisconnected.set(this, false);
474
+ _HyperLiquidProvider_disconnectOperationsInFlight.set(this, 0);
475
+ _HyperLiquidProvider_disconnectOperationPromise.set(this, null);
476
+ _HyperLiquidProvider_lifecycleGeneration.set(this, 0);
332
477
  // Last known-good market list for stale fallback when every enabled DEX fails in one fetch window.
333
478
  _HyperLiquidProvider_cachedMarketDataWithPrices.set(this, null);
334
479
  // Session cache for spot metadata (contains token info for HIP-3 collateral checks)
@@ -361,12 +506,9 @@ class HyperLiquidProvider {
361
506
  _HyperLiquidProvider_hip3Enabled.set(this, void 0);
362
507
  _HyperLiquidProvider_allowlistMarkets.set(this, void 0);
363
508
  _HyperLiquidProvider_blocklistMarkets.set(this, void 0);
364
- // Emergency kill-switch for the Unified Account migration flow. Defaults
365
- // to true and is the expected production state after HL's DEX Abstraction
366
- // deprecation. Kept as a constructor option (not removed) so we can
367
- // disable the migration via a hot-fix release if a regression surfaces
368
- // in the wild — flipping this to false reverts to the legacy programmatic
369
- // HIP-3 transfer path that already lives in the codebase.
509
+ // Legacy rollback switch. MetaMask production supports unified account and
510
+ // portfolio margin. The false branch and its manual HIP-3 transfers remain
511
+ // temporarily for rollback compatibility and will be removed separately.
370
512
  _HyperLiquidProvider_useUnifiedAccount.set(this, void 0);
371
513
  // True once DEX discovery has succeeded with real data (not a fallback).
372
514
  // When false, #ensureReadyPromise is reset after each init so the next
@@ -393,6 +535,10 @@ class HyperLiquidProvider {
393
535
  // one cancel can reach every rung. Cleared on disconnect; the rung IDs are
394
536
  // also returned to the caller as `OrderResult.childOrderIds`.
395
537
  _HyperLiquidProvider_scaleOrderGroups.set(this, new Map());
538
+ // Prevent a stale open-order cache from recreating a group that this
539
+ // provider lifecycle already canceled successfully.
540
+ _HyperLiquidProvider_cancelledScaleOrderGroups.set(this, new Set());
541
+ _HyperLiquidProvider_trackedTwapOrders.set(this, void 0);
396
542
  // Chase sessions running on this provider instance, keyed by session handle.
397
543
  // Each owns a pending timer, so disconnect has to stop them.
398
544
  _HyperLiquidProvider_chaseSessions.set(this, new Map());
@@ -415,11 +561,9 @@ class HyperLiquidProvider {
415
561
  // transport, so simultaneous cancel/replace ticks would create avoidable
416
562
  // signed traffic and can starve a foreground placement.
417
563
  _HyperLiquidProvider_chaseTickQueue.set(this, Promise.resolve());
418
- // Bumped by every teardown that clears the chase registry. A placement
419
- // captures it before its round trips and refuses to register afterwards if it
420
- // has moved: a session registered after a disconnect would schedule a
421
- // background timer against a provider that has already been torn down.
422
- _HyperLiquidProvider_chaseGeneration.set(this, 0);
564
+ // Every strategy captures this before its round trips. A changed generation
565
+ // prevents a late response from registering handles after disconnect.
566
+ _HyperLiquidProvider_strategyGeneration.set(this, 0);
423
567
  // Reference-counted because suspension and disconnect may overlap. One
424
568
  // lifecycle owner finishing must not reopen placement while another still
425
569
  // drains or tears down the provider.
@@ -434,27 +578,35 @@ class HyperLiquidProvider {
434
578
  _HyperLiquidProvider_subscriptionBuilderAddressTestnet.set(this, void 0);
435
579
  _HyperLiquidProvider_subscriptionBuilderAddressMainnet.set(this, void 0);
436
580
  _HyperLiquidProvider_priceDeviationLimit.set(this, void 0);
581
+ _HyperLiquidProvider_onChaseOrderMaxDistanceReached.set(this, void 0);
437
582
  /**
438
583
  * Ensure provider is ready for TRADING operations (signing required)
439
584
  *
440
- * This method performs additional setup that requires user signatures:
585
+ * This method performs shared setup that may require user signatures:
441
586
  * - DEX abstraction enablement (for HIP-3 auto-transfers)
442
- * - Builder fee approval (required for orders)
443
587
  * - Referral code setup (attribution)
588
+ * - Builder fee approval when the whole action carries a builder context
444
589
  *
445
590
  * These operations are DEFERRED from ensureReady() to avoid hardware wallet prompt spam
446
591
  * when users are just viewing the Perps section (critical for hardware wallets).
447
592
  *
448
- * Call this method before any trading operation (placeOrder, cancelOrder, etc.)
593
+ * General trading readiness is independent of builder approval. Native TWAP
594
+ * and cancellations call this with `requiresBuilderFee: false`.
449
595
  */
450
596
  _HyperLiquidProvider_tradingSetupPromise.set(this, null);
451
597
  _HyperLiquidProvider_tradingSetupComplete.set(this, false);
598
+ _HyperLiquidProvider_builderFeeSetupPromises.set(this, new Map());
452
599
  __classPrivateFieldSet(this, _HyperLiquidProvider_deps, options.platformDependencies, "f");
453
600
  __classPrivateFieldSet(this, _HyperLiquidProvider_messenger, options.messenger, "f");
601
+ const trackedTwapOrders = __classPrivateFieldGet(_a, _a, "f", _HyperLiquidProvider_trackedTwapOrdersByMessenger).get(__classPrivateFieldGet(this, _HyperLiquidProvider_messenger, "f")) ??
602
+ new Map();
603
+ __classPrivateFieldGet(_a, _a, "f", _HyperLiquidProvider_trackedTwapOrdersByMessenger).set(__classPrivateFieldGet(this, _HyperLiquidProvider_messenger, "f"), trackedTwapOrders);
604
+ __classPrivateFieldSet(this, _HyperLiquidProvider_trackedTwapOrders, trackedTwapOrders, "f");
454
605
  __classPrivateFieldSet(this, _HyperLiquidProvider_builderAddressTestnet, options.builderAddressTestnet, "f");
455
606
  __classPrivateFieldSet(this, _HyperLiquidProvider_builderAddressMainnet, options.builderAddressMainnet, "f");
456
607
  __classPrivateFieldSet(this, _HyperLiquidProvider_subscriptionBuilderAddressTestnet, options.subscriptionBuilderAddressTestnet, "f");
457
608
  __classPrivateFieldSet(this, _HyperLiquidProvider_subscriptionBuilderAddressMainnet, options.subscriptionBuilderAddressMainnet, "f");
609
+ __classPrivateFieldSet(this, _HyperLiquidProvider_onChaseOrderMaxDistanceReached, options.onChaseOrderMaxDistanceReached, "f");
458
610
  __classPrivateFieldSet(this, _HyperLiquidProvider_priceDeviationLimit, options.priceDeviationLimit ??
459
611
  hyperLiquidConfig_js_1.HYPERLIQUID_CONFIG.OraclePriceDeviationLimit, "f");
460
612
  const isTestnet = options.isTestnet ?? false;
@@ -504,6 +656,56 @@ class HyperLiquidProvider {
504
656
  isTestnet,
505
657
  });
506
658
  }
659
+ /**
660
+ * Return provider-owned strategy support for a routed market.
661
+ * HyperLiquid metadata has no per-market strategy flags. This provider
662
+ * advertises its implemented strategies uniformly after confirming the
663
+ * routed market exists.
664
+ *
665
+ * @param params - Required market route context.
666
+ * @returns Supported strategy order types.
667
+ */
668
+ async getOrderCapabilities(params) {
669
+ if (!(0, capabilitySymbols_js_1.isValidCapabilitySymbol)(params.symbol, { allowProviderRoute: true })) {
670
+ return {
671
+ status: 'unavailable',
672
+ providerId: this.protocolId,
673
+ reason: 'invalid_symbol',
674
+ };
675
+ }
676
+ const { dex } = (0, hyperLiquidAdapter_js_1.parseAssetName)(params.symbol);
677
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_isDisconnected, "f")) {
678
+ return {
679
+ status: 'unavailable',
680
+ providerId: this.protocolId,
681
+ reason: 'provider_unavailable',
682
+ };
683
+ }
684
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
685
+ try {
686
+ const marketExists = (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getFreshOrderCapabilityMarkets).call(this, dex)).some((market) => market.name === params.symbol);
687
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Order capability read');
688
+ return marketExists
689
+ ? hyperLiquidConfig_js_1.HYPERLIQUID_ORDER_CAPABILITIES
690
+ : {
691
+ status: 'unavailable',
692
+ providerId: this.protocolId,
693
+ reason: 'market_not_found',
694
+ };
695
+ }
696
+ catch (error) {
697
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Order capabilities unavailable', {
698
+ symbol: params.symbol,
699
+ error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.getOrderCapabilities')
700
+ .message,
701
+ });
702
+ return {
703
+ status: 'unavailable',
704
+ providerId: this.protocolId,
705
+ reason: 'provider_unavailable',
706
+ };
707
+ }
708
+ }
507
709
  /**
508
710
  * Get fills using WebSocket cache first, falling back to REST API
509
711
  * OPTIMIZATION: Uses cached fills when available (0 API weight), only calls REST on cache miss
@@ -762,7 +964,10 @@ class HyperLiquidProvider {
762
964
  // Ensure provider is ready for trading (includes signing operations).
763
965
  // Kept after validation so invalid orders never trigger signature prompts
764
966
  // (builder-fee approval, DEX abstraction enablement, etc.).
765
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
967
+ const { chargesMetamaskBuilderFee } = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, params);
968
+ const builderFeeSetupContext = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, {
969
+ requiresBuilderFee: chargesMetamaskBuilderFee,
970
+ });
766
971
  // Debug: Log asset map state before order placement
767
972
  const allMapKeys = Array.from(__classPrivateFieldGet(this, _HyperLiquidProvider_symbolToAssetId, "f").keys());
768
973
  const hip3Keys = allMapKeys.filter((key) => key.includes(':'));
@@ -867,6 +1072,8 @@ class HyperLiquidProvider {
867
1072
  transferInfo,
868
1073
  symbol: params.symbol,
869
1074
  assetId,
1075
+ chargesMetamaskBuilderFee,
1076
+ builderFeeSetupContext,
870
1077
  });
871
1078
  }
872
1079
  catch (error) {
@@ -933,6 +1140,54 @@ class HyperLiquidProvider {
933
1140
  });
934
1141
  }
935
1142
  }
1143
+ /**
1144
+ * Read native TWAP schedules and their slice fills from the venue.
1145
+ *
1146
+ * @returns Current and terminal TWAP lifecycle records, newest first.
1147
+ */
1148
+ async getTwapOrders() {
1149
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureClientsInitialized).call(this);
1150
+ __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").ensureInitialized();
1151
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
1152
+ const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient();
1153
+ const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
1154
+ const [history, sliceFills] = await Promise.all([
1155
+ infoClient.twapHistory({ user: userAddress }),
1156
+ infoClient.userTwapSliceFills({ user: userAddress }),
1157
+ ]);
1158
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'TWAP lifecycle read');
1159
+ const fillsByTwapId = new Map();
1160
+ for (const sliceFill of sliceFills) {
1161
+ const existing = fillsByTwapId.get(sliceFill.twapId) ?? [];
1162
+ existing.push(sliceFill);
1163
+ fillsByTwapId.set(sliceFill.twapId, existing);
1164
+ }
1165
+ const now = Date.now();
1166
+ const ordersById = new Map();
1167
+ for (const historyEntry of history) {
1168
+ const order = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_adaptTwapOrder).call(this, {
1169
+ historyEntry,
1170
+ sliceFills: historyEntry.twapId === undefined
1171
+ ? []
1172
+ : (fillsByTwapId.get(historyEntry.twapId) ?? []),
1173
+ now,
1174
+ });
1175
+ if (!order) {
1176
+ continue;
1177
+ }
1178
+ const existing = ordersById.get(order.orderId);
1179
+ if (!existing || order.lastUpdated >= existing.lastUpdated) {
1180
+ ordersById.set(order.orderId, order);
1181
+ }
1182
+ }
1183
+ const orders = [...ordersById.values()].sort((left, right) => right.startedAt - left.startedAt);
1184
+ const network = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode() ? 'testnet' : 'mainnet';
1185
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceTerminalHip3Twaps).call(this, orders, {
1186
+ network,
1187
+ userAddress,
1188
+ });
1189
+ return orders;
1190
+ }
936
1191
  /**
937
1192
  * Return stable client-facing snapshots for every Chase session retained by
938
1193
  * this provider instance. A read may refresh the current child order's
@@ -950,12 +1205,26 @@ class HyperLiquidProvider {
950
1205
  session.lastSnapshotSizeRefreshAt = now;
951
1206
  const snapshotOrderId = session.orderId;
952
1207
  try {
953
- const liveRemainder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, snapshotOrderId);
954
- if (liveRemainder !== null &&
955
- session.orderId === snapshotOrderId &&
1208
+ const liveRemainder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, snapshotOrderId, CHASE_ORDER_STATUS_RETRY_COUNT);
1209
+ if (session.orderId === snapshotOrderId &&
956
1210
  session.pendingReplacement === null) {
957
- Object.assign(session, { size: liveRemainder });
958
- snapshotRemainingSize = liveRemainder;
1211
+ if (liveRemainder.terminalStatus === null) {
1212
+ if (liveRemainder.remainingSize !== null) {
1213
+ session.size = liveRemainder.remainingSize;
1214
+ snapshotRemainingSize = liveRemainder.remainingSize;
1215
+ }
1216
+ }
1217
+ else {
1218
+ session.orderId = null;
1219
+ session.size = liveRemainder.remainingSize ?? '0';
1220
+ session.active = false;
1221
+ if (session.timer) {
1222
+ clearTimeout(session.timer);
1223
+ session.timer = null;
1224
+ }
1225
+ session.status = liveRemainder.terminalStatus;
1226
+ snapshotRemainingSize = session.size;
1227
+ }
959
1228
  }
960
1229
  }
961
1230
  catch (error) {
@@ -966,6 +1235,12 @@ class HyperLiquidProvider {
966
1235
  });
967
1236
  }
968
1237
  }
1238
+ if (!session.active && session.hip3Transfer) {
1239
+ const rebalanced = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
1240
+ if (rebalanced && session.removeAfterRebalance) {
1241
+ __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").delete(handle);
1242
+ }
1243
+ }
969
1244
  const arrival = Number.parseFloat(session.arrivalPrice);
970
1245
  const resting = Number.parseFloat(session.restingPrice);
971
1246
  const adverseDistance = session.isBuy
@@ -1003,9 +1278,6 @@ class HyperLiquidProvider {
1003
1278
  */
1004
1279
  async suspendChaseOrders() {
1005
1280
  __classPrivateFieldSet(this, _HyperLiquidProvider_chasePlacementBlockers, __classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") + 1, "f");
1006
- // Abort a placement that passed the blocker check but has not completed
1007
- // its final generation check; the waiter below then drains its cleanup.
1008
- __classPrivateFieldSet(this, _HyperLiquidProvider_chaseGeneration, __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f") + 1, "f");
1009
1281
  try {
1010
1282
  await Promise.all([...__classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementWaiters, "f")]);
1011
1283
  // Clear timers without deactivating sessions, then let any tick already
@@ -1188,10 +1460,10 @@ class HyperLiquidProvider {
1188
1460
  ? params.newOrder.clientOrderId
1189
1461
  : undefined,
1190
1462
  };
1191
- // Every refusal is behind us, so the setup that may prompt for signatures
1192
- // and write builder-fee/referral approvals can run now — a rejected edit
1193
- // costs the caller nothing, matching placeOrder and updatePositionTPSL.
1194
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
1463
+ // Every refusal is behind us, so shared trading readiness can run now.
1464
+ // HyperLiquid's modify action has no builder field and must not request a
1465
+ // builder-fee approval.
1466
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
1195
1467
  // Submit modification via SDK
1196
1468
  const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
1197
1469
  const result = await exchangeClient.modify({
@@ -1256,24 +1528,25 @@ class HyperLiquidProvider {
1256
1528
  if (owningChase) {
1257
1529
  __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_stopChaseSession).call(this, owningChase[0]);
1258
1530
  await __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f");
1259
- return await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelChaseOrder).call(this, {
1531
+ const result = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelChaseOrder).call(this, {
1260
1532
  ...params,
1261
1533
  orderId: owningChase[0],
1262
1534
  orderType: 'chase',
1263
1535
  });
1536
+ return { ...result, orderId: params.orderId };
1264
1537
  }
1265
1538
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Canceling order:', params);
1266
1539
  // Hydrate the asset map before coin validation so a cold start (e.g.
1267
1540
  // service-worker restart with an empty prefetch map) can self-heal
1268
1541
  // without signature prompts on invalid cancels. Trading setup (builder
1269
- // fee, referral, unified account) runs only after validation passes,
1270
- // matching placeOrder / editOrder.
1542
+ // referral and unified-account setup runs only after validation passes,
1543
+ // matching placeOrder / editOrder. Cancel has no builder field.
1271
1544
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReady).call(this);
1272
1545
  const coinValidation = (0, hyperLiquidValidation_js_1.validateCoinExists)(params.symbol, __classPrivateFieldGet(this, _HyperLiquidProvider_symbolToAssetId, "f"));
1273
1546
  if (!coinValidation.isValid) {
1274
1547
  throw new Error(coinValidation.error);
1275
1548
  }
1276
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
1549
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
1277
1550
  const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
1278
1551
  const asset = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
1279
1552
  symbol: params.symbol,
@@ -1297,10 +1570,10 @@ class HyperLiquidProvider {
1297
1570
  // HyperLiquid usually rejects a cancel without throwing: the status entry
1298
1571
  // carries the raw exchange string (e.g. "multi-sig required"). Map it the
1299
1572
  // same way as a thrown rejection so callers get a standardized code
1300
- // instead of a generic message. The SDK types every status as 'success',
1301
- // so the rejection shape needs the same cast cancelOrders already uses.
1302
- const rawError = status?.error ??
1303
- 'Order cancellation failed';
1573
+ // instead of a generic message.
1574
+ const rawError = isStatusObject(status) && typeof status.error === 'string'
1575
+ ? status.error
1576
+ : 'Order cancellation failed';
1304
1577
  return (0, hyperLiquidValidation_js_1.createErrorResult)(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mapError).call(this, new Error(rawError)), {
1305
1578
  success: false,
1306
1579
  orderId: params.orderId,
@@ -1312,7 +1585,10 @@ class HyperLiquidProvider {
1312
1585
  orderId: params.orderId,
1313
1586
  coin: params.symbol,
1314
1587
  }));
1315
- return (0, hyperLiquidValidation_js_1.createErrorResult)(mappedError, { success: false });
1588
+ return (0, hyperLiquidValidation_js_1.createErrorResult)(mappedError, {
1589
+ success: false,
1590
+ orderId: params.orderId,
1591
+ });
1316
1592
  }
1317
1593
  }
1318
1594
  /**
@@ -1323,87 +1599,126 @@ class HyperLiquidProvider {
1323
1599
  * @returns A promise that resolves to the result.
1324
1600
  */
1325
1601
  async cancelOrders(params) {
1602
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Batch canceling orders:', {
1603
+ count: params.length,
1604
+ });
1605
+ if (params.length === 0) {
1606
+ return {
1607
+ success: false,
1608
+ successCount: 0,
1609
+ failureCount: 0,
1610
+ results: [],
1611
+ };
1612
+ }
1613
+ const results = params.map((order) => ({
1614
+ orderId: order.orderId,
1615
+ symbol: order.symbol,
1616
+ success: false,
1617
+ error: perpsErrorCodes_js_1.PERPS_ERROR_CODES.BATCH_CANCEL_FAILED,
1618
+ }));
1619
+ // Resolve Chase ownership before the first await. A queued reprice may
1620
+ // replace the child ID, but the stable session handle still reaches it.
1621
+ const chaseSessions = [...__classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").entries()];
1622
+ const chaseRoutes = params.flatMap((order, index) => {
1623
+ const owner = chaseSessions.find(([, session]) => session.orderId === order.orderId ||
1624
+ session.replacingOrderId === order.orderId);
1625
+ return owner ? [{ handle: owner[0], index, order }] : [];
1626
+ });
1627
+ const chaseIndexes = new Set(chaseRoutes.map(({ index }) => index));
1326
1628
  try {
1327
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Batch canceling orders:', {
1328
- count: params.length,
1329
- });
1330
- if (params.length === 0) {
1331
- return {
1332
- success: false,
1333
- successCount: 0,
1334
- failureCount: 0,
1335
- results: [],
1336
- };
1629
+ for (const handle of new Set(chaseRoutes.map((route) => route.handle))) {
1630
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_stopChaseSession).call(this, handle);
1337
1631
  }
1338
- // Ensure provider is ready for trading (includes signing operations)
1339
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
1340
- const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
1341
- // Map orders to SDK format and validate coins
1342
- const cancelRequests = await Promise.all(params.map(async (order) => {
1343
- const asset = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
1632
+ if (chaseRoutes.length > 0) {
1633
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f");
1634
+ }
1635
+ for (const { handle, index, order } of chaseRoutes) {
1636
+ const result = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelStrategyOrder).call(this, {
1637
+ orderId: handle,
1344
1638
  symbol: order.symbol,
1345
- dexName: (0, hyperLiquidAdapter_js_1.parseAssetName)(order.symbol).dex,
1639
+ orderType: 'chase',
1346
1640
  });
1347
- return {
1348
- a: asset,
1349
- o: parseInt(order.orderId, 10),
1641
+ results[index] = {
1642
+ orderId: order.orderId,
1643
+ symbol: order.symbol,
1644
+ success: result.success,
1645
+ ...(result.error === undefined ? {} : { error: result.error }),
1350
1646
  };
1351
- }));
1352
- // Single batch API call
1353
- const result = await exchangeClient.cancel({
1354
- cancels: cancelRequests,
1355
- });
1356
- // Parse response statuses (one per order)
1357
- const { statuses } = result.response.data;
1358
- const successCount = statuses.filter((status) => status === 'success').length;
1359
- const failureCount = statuses.length - successCount;
1360
- return {
1361
- success: successCount > 0,
1362
- successCount,
1363
- failureCount,
1364
- results: statuses.map((status, index) => {
1365
- // Map each per-status rejection the same way cancelOrder does, so a
1366
- // batch cancel reports standardized codes rather than raw exchange
1367
- // strings for the rejections this provider recognizes.
1368
- const statusError = status?.error;
1647
+ }
1648
+ const ordinaryOrders = params.flatMap((order, index) => chaseIndexes.has(index) ? [] : [{ index, order }]);
1649
+ if (ordinaryOrders.length > 0) {
1650
+ // Cancellation carries no builder context, so it must not prompt for a
1651
+ // fee approval that the action cannot use.
1652
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
1653
+ const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
1654
+ const cancelRequests = await Promise.all(ordinaryOrders.map(async ({ order }) => {
1655
+ const asset = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
1656
+ symbol: order.symbol,
1657
+ dexName: (0, hyperLiquidAdapter_js_1.parseAssetName)(order.symbol).dex,
1658
+ });
1369
1659
  return {
1370
- orderId: params[index].orderId,
1371
- symbol: params[index].symbol,
1372
- success: status === 'success',
1373
- error: statusError
1374
- ? __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mapError).call(this, new Error(statusError)).message
1375
- : undefined,
1660
+ a: asset,
1661
+ o: parseInt(order.orderId, 10),
1376
1662
  };
1377
- }),
1378
- };
1663
+ }));
1664
+ const result = await exchangeClient.cancel({
1665
+ cancels: cancelRequests,
1666
+ });
1667
+ const statuses = result.response?.data?.statuses ?? [];
1668
+ if (result.status === 'ok' &&
1669
+ statuses.length === ordinaryOrders.length) {
1670
+ ordinaryOrders.forEach(({ index, order }, statusIndex) => {
1671
+ const status = statuses[statusIndex];
1672
+ const success = status === 'success';
1673
+ const statusError = isStatusObject(status) && typeof status.error === 'string'
1674
+ ? status.error
1675
+ : undefined;
1676
+ results[index] = {
1677
+ orderId: order.orderId,
1678
+ symbol: order.symbol,
1679
+ success,
1680
+ ...(success
1681
+ ? {}
1682
+ : {
1683
+ error: statusError === undefined
1684
+ ? perpsErrorCodes_js_1.PERPS_ERROR_CODES.BATCH_CANCEL_FAILED
1685
+ : __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mapError).call(this, new Error(statusError)).message,
1686
+ }),
1687
+ };
1688
+ });
1689
+ }
1690
+ }
1379
1691
  }
1380
1692
  catch (error) {
1381
1693
  const mappedError = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mapError).call(this, error);
1382
1694
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").logger.error(mappedError, await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getTradingErrorContext).call(this, 'cancelOrders', mappedError, {
1383
1695
  orderCount: params.length,
1384
1696
  }));
1385
- // Return all orders as failed
1386
- return {
1387
- success: false,
1388
- successCount: 0,
1389
- failureCount: params.length,
1390
- results: params.map((order) => ({
1391
- orderId: order.orderId,
1392
- symbol: order.symbol,
1393
- success: false,
1394
- error: error instanceof Error
1395
- ? mappedError.message
1396
- : perpsErrorCodes_js_1.PERPS_ERROR_CODES.BATCH_CANCEL_FAILED,
1397
- })),
1398
- };
1697
+ for (const result of results) {
1698
+ if (!result.success &&
1699
+ result.error === perpsErrorCodes_js_1.PERPS_ERROR_CODES.BATCH_CANCEL_FAILED) {
1700
+ result.error =
1701
+ error instanceof Error
1702
+ ? mappedError.message
1703
+ : perpsErrorCodes_js_1.PERPS_ERROR_CODES.BATCH_CANCEL_FAILED;
1704
+ }
1705
+ }
1399
1706
  }
1707
+ const successCount = results.filter(({ success }) => success).length;
1708
+ return {
1709
+ success: successCount > 0,
1710
+ successCount,
1711
+ failureCount: results.length - successCount,
1712
+ results,
1713
+ };
1400
1714
  }
1401
1715
  async closePositions(params) {
1402
1716
  // Declare outside try block so it's accessible in catch block
1403
1717
  let positionsToClose = [];
1404
1718
  try {
1405
- // Ensure provider is ready for trading (includes signing operations)
1406
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
1719
+ // Batch preparation needs trading readiness but not builder approval yet.
1720
+ // The provider-owned market policy is resolved from the positions below.
1721
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
1407
1722
  // Get all current positions from cache (avoids 429 rate limiting)
1408
1723
  const positions = await this.getPositions();
1409
1724
  // Filter positions based on params
@@ -1441,6 +1756,7 @@ class HyperLiquidProvider {
1441
1756
  // Build orders array, plus the positions each order closes so response
1442
1757
  // statuses stay index-aligned when a position is skipped below
1443
1758
  const orders = [];
1759
+ const feePolicyContexts = [];
1444
1760
  const orderedPositions = [];
1445
1761
  // Positions no order could be built for. Reported as failures so a caller
1446
1762
  // cannot read "closed everything" from a result that left one open.
@@ -1515,6 +1831,13 @@ class HyperLiquidProvider {
1515
1831
  r: true, // reduceOnly
1516
1832
  t: { limit: { tif: 'Ioc' } }, // Immediate or cancel for market-like execution
1517
1833
  });
1834
+ feePolicyContexts.push({
1835
+ symbol: position.symbol,
1836
+ isBuy,
1837
+ size: formattedSize,
1838
+ orderType: 'market',
1839
+ reduceOnly: true,
1840
+ });
1518
1841
  orderedPositions.push(position);
1519
1842
  orderedHip3Transfers.push(hip3Transfer);
1520
1843
  }
@@ -1529,11 +1852,19 @@ class HyperLiquidProvider {
1529
1852
  results: skippedResults,
1530
1853
  };
1531
1854
  }
1855
+ // HyperLiquid accepts one builder context for the whole close batch.
1856
+ // Resolve every position context and charge the batch when any included
1857
+ // policy charges, preserving deterministic behavior for future policies
1858
+ // that vary by market or route.
1859
+ const chargesMetamaskBuilderFee = feePolicyContexts.some((context) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, context).chargesMetamaskBuilderFee);
1860
+ const builder = chargesMetamaskBuilderFee
1861
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this, await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: true }))
1862
+ : undefined;
1532
1863
  // Single batch API call
1533
1864
  const result = await exchangeClient.order({
1534
1865
  orders,
1535
1866
  grouping: 'na',
1536
- builder: await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this),
1867
+ ...(builder && { builder }),
1537
1868
  });
1538
1869
  // Parse response statuses (one per order)
1539
1870
  const { statuses } = result.response.data;
@@ -1647,11 +1978,11 @@ class HyperLiquidProvider {
1647
1978
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReady).call(this);
1648
1979
  // Use live position (from WebSocket) if available, otherwise fetch via REST
1649
1980
  // Preferring WebSocket data avoids rate limiting issues with the REST API
1650
- let position = livePosition;
1651
- if (position) {
1981
+ let fetchedPosition;
1982
+ if (livePosition) {
1652
1983
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Using live position from WebSocket', {
1653
- symbol: position.symbol,
1654
- size: position.size,
1984
+ symbol: livePosition.symbol,
1985
+ size: livePosition.size,
1655
1986
  });
1656
1987
  }
1657
1988
  else {
@@ -1667,8 +1998,9 @@ class HyperLiquidProvider {
1667
1998
  }));
1668
1999
  throw error;
1669
2000
  }
1670
- position = positions.find((pos) => pos.symbol === symbol);
2001
+ fetchedPosition = positions.find((pos) => pos.symbol === symbol);
1671
2002
  }
2003
+ const position = livePosition ?? fetchedPosition;
1672
2004
  if (!position) {
1673
2005
  throw new Error(`No position found for ${symbol}`);
1674
2006
  }
@@ -1732,17 +2064,117 @@ class HyperLiquidProvider {
1732
2064
  error: precision.error,
1733
2065
  };
1734
2066
  }
1735
- // Everything is validated: only now run the trading setup that can prompt
1736
- // for signatures and write the referral / builder-fee approvals.
1737
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
1738
- // Cancel existing TP/SL orders for this position
1739
- // OPTIMIZATION: Use WebSocket cache first (0 weight), fall back to single-DEX REST (20 weight)
1740
- // Previously: queryUserDataAcrossDexs queried ALL DEXs (20 weight × N DEXs = 40+ weight)
1741
2067
  const assetId = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
1742
2068
  symbol,
1743
2069
  dexName,
1744
2070
  });
2071
+ const fullSize = perpsConfig_js_1.TP_SL_CONFIG.UsePositionBoundTpsl && !isPartialTpsl
2072
+ ? '0'
2073
+ : (0, hyperLiquidAdapter_js_1.formatHyperLiquidSize)({
2074
+ size: positionSize,
2075
+ szDecimals: assetInfo.szDecimals,
2076
+ });
2077
+ // Partial TP/SL orders carry their own size; the rest cover the position.
2078
+ // A partial size that rounds away at the asset precision is rejected
2079
+ // rather than sent as '0', which the exchange reads as whole-position.
2080
+ const resolveTpslSize = (tpslSize) => tpslSize === undefined
2081
+ ? fullSize
2082
+ : (0, orderCalculations_js_1.formatPartialTpslSize)({
2083
+ size: parseFloat(tpslSize),
2084
+ szDecimals: assetInfo.szDecimals,
2085
+ });
2086
+ // HyperLiquid accepts one builder context for the whole TP/SL action.
2087
+ // Position TP/SL has no parent policy to inherit, so resolve every
2088
+ // included trigger and charge the batch when any child policy charges.
2089
+ const feePolicyContexts = [
2090
+ ...(takeProfitPrice
2091
+ ? [
2092
+ {
2093
+ symbol,
2094
+ isBuy: !isLong,
2095
+ size: resolveTpslSize(takeProfitSize),
2096
+ orderType: 'take_profit_limit',
2097
+ price: takeProfitPrice,
2098
+ triggerPrice: takeProfitPrice,
2099
+ reduceOnly: true,
2100
+ },
2101
+ ]
2102
+ : []),
2103
+ ...(stopLossPrice
2104
+ ? [
2105
+ {
2106
+ symbol,
2107
+ isBuy: !isLong,
2108
+ size: resolveTpslSize(stopLossSize),
2109
+ orderType: 'stop_market',
2110
+ triggerPrice: stopLossPrice,
2111
+ reduceOnly: true,
2112
+ },
2113
+ ]
2114
+ : []),
2115
+ ];
2116
+ const replacementChargesMetamaskBuilderFee = feePolicyContexts.some((feePolicyContext) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, feePolicyContext)
2117
+ .chargesMetamaskBuilderFee);
2118
+ // Cancel existing TP/SL orders for this position
2119
+ // OPTIMIZATION: Use WebSocket cache first (0 weight), fall back to single-DEX REST (20 weight)
2120
+ // Previously: queryUserDataAcrossDexs queried ALL DEXs (20 weight × N DEXs = 40+ weight)
1745
2121
  let cancelRequests = [];
2122
+ const restorablePositionTpslOrders = [];
2123
+ const restorableStandaloneTpslOrders = [];
2124
+ // Every replacement must cancel first so old and new reduce-only triggers
2125
+ // cannot execute together. Retain the exact trigger definitions so a
2126
+ // definitively failed replacement can restore confirmed cancellations.
2127
+ const captureRestorableTpslOrders = (tpslOrders) => {
2128
+ if (!takeProfitPrice && !stopLossPrice) {
2129
+ return;
2130
+ }
2131
+ tpslOrders.forEach((order) => {
2132
+ const orderId = Number(order.orderId);
2133
+ const orderType = order.triggerOrderType;
2134
+ const triggerPrice = order.triggerPrice ?? order.price;
2135
+ if (!Number.isSafeInteger(orderId) || !orderType || !triggerPrice) {
2136
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.TPSL_UPDATE_FAILED);
2137
+ }
2138
+ const size = order.isPositionTpsl ? '0' : order.remainingSize;
2139
+ const isBuy = order.side === 'buy';
2140
+ const feePolicyContext = {
2141
+ symbol,
2142
+ isBuy,
2143
+ size,
2144
+ orderType,
2145
+ triggerPrice,
2146
+ reduceOnly: true,
2147
+ ...((0, orderTypes_js_1.isLimitExecutionOrderType)(orderType) && {
2148
+ price: order.price,
2149
+ }),
2150
+ };
2151
+ const restorableOrder = {
2152
+ orderId,
2153
+ order: {
2154
+ a: assetId,
2155
+ b: isBuy,
2156
+ p: order.price,
2157
+ s: size,
2158
+ r: true,
2159
+ t: {
2160
+ trigger: {
2161
+ isMarket: !(0, orderTypes_js_1.isLimitExecutionOrderType)(orderType),
2162
+ triggerPx: triggerPrice,
2163
+ tpsl: (0, orderTypes_js_1.getTriggerDirection)(orderType) === 'stop' ? 'sl' : 'tp',
2164
+ },
2165
+ },
2166
+ },
2167
+ chargesMetamaskBuilderFee: __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, feePolicyContext)
2168
+ .chargesMetamaskBuilderFee,
2169
+ };
2170
+ if (order.isPositionTpsl) {
2171
+ restorablePositionTpslOrders.push(restorableOrder);
2172
+ }
2173
+ else {
2174
+ restorableStandaloneTpslOrders.push(restorableOrder);
2175
+ }
2176
+ });
2177
+ };
1746
2178
  // Use atomic getter to prevent race condition between check and get
1747
2179
  const cachedOrders = __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").getOrdersCacheIfInitialized();
1748
2180
  // Replacing TP/SL has to consider standalone ('na' grouping) triggers —
@@ -1769,16 +2201,16 @@ class HyperLiquidProvider {
1769
2201
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(cachedOrders === null
1770
2202
  ? 'WebSocket cache not initialized, falling back to single-DEX REST query'
1771
2203
  : 'TP/SL update needs parent/child order context: using single-DEX REST query', { dex: dexName ?? 'main', isPartialTpsl });
1772
- const orders = await infoClient.frontendOpenOrders({
2204
+ const openOrders = await infoClient.frontendOpenOrders({
1773
2205
  user: userAddress,
1774
2206
  dex: dexName ?? undefined,
1775
2207
  });
1776
2208
  // Orders that belong to a pending parent order (normalTpsl children) are
1777
2209
  // also listed at the top level, so collect their IDs to exclude them:
1778
2210
  // they protect that pending order, not this position.
1779
- const childOrderIds = collectChildOrderIds(orders);
2211
+ const childOrderIds = collectChildOrderIds(openOrders);
1780
2212
  // Filter using raw SDK response properties
1781
- const tpslOrders = orders.filter((order) => order.coin === symbol &&
2213
+ const tpslOrders = openOrders.filter((order) => order.coin === symbol &&
1782
2214
  order.reduceOnly &&
1783
2215
  // Position-bound TP/SL always qualifies, and so do standalone
1784
2216
  // triggers on this market (they belong to the position too, whether
@@ -1790,6 +2222,7 @@ class HyperLiquidProvider {
1790
2222
  order.isTrigger &&
1791
2223
  (order.orderType.includes('Take Profit') ||
1792
2224
  order.orderType.includes('Stop')));
2225
+ captureRestorableTpslOrders(tpslOrders.map((order) => (0, hyperLiquidAdapter_js_1.adaptOrderFromSDK)(order, position)));
1793
2226
  cancelRequests = tpslOrders.map((order) => ({
1794
2227
  a: assetId,
1795
2228
  o: order.oid,
@@ -1813,36 +2246,14 @@ class HyperLiquidProvider {
1813
2246
  order.detailedOrderType &&
1814
2247
  (order.detailedOrderType.includes('Take Profit') ||
1815
2248
  order.detailedOrderType.includes('Stop')));
2249
+ captureRestorableTpslOrders(tpslOrders);
1816
2250
  cancelRequests = tpslOrders.map((order) => ({
1817
2251
  a: assetId,
1818
2252
  o: parseInt(order.orderId, 10),
1819
2253
  }));
1820
2254
  }
1821
- if (cancelRequests.length > 0) {
1822
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`Canceling ${cancelRequests.length} existing TP/SL orders for ${symbol}`);
1823
- const cancelResult = await exchangeClient.cancel({
1824
- cancels: cancelRequests,
1825
- });
1826
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Cancel result:', cancelResult);
1827
- }
1828
- // assetId already validated above when building cancelRequests
1829
2255
  // Build orders array for TP/SL
1830
2256
  const orders = [];
1831
- const fullSize = perpsConfig_js_1.TP_SL_CONFIG.UsePositionBoundTpsl && !isPartialTpsl
1832
- ? '0'
1833
- : (0, hyperLiquidAdapter_js_1.formatHyperLiquidSize)({
1834
- size: positionSize,
1835
- szDecimals: assetInfo.szDecimals,
1836
- });
1837
- // Partial TP/SL orders carry their own size; the rest cover the position.
1838
- // A partial size that rounds away at the asset precision is rejected
1839
- // rather than sent as '0', which the exchange reads as whole-position.
1840
- const resolveTpslSize = (tpslSize) => tpslSize === undefined
1841
- ? fullSize
1842
- : (0, orderCalculations_js_1.formatPartialTpslSize)({
1843
- size: parseFloat(tpslSize),
1844
- szDecimals: assetInfo.szDecimals,
1845
- });
1846
2257
  // Take Profit order
1847
2258
  if (takeProfitPrice) {
1848
2259
  const tpOrder = {
@@ -1891,24 +2302,184 @@ class HyperLiquidProvider {
1891
2302
  };
1892
2303
  orders.push(slOrder);
1893
2304
  }
1894
- // If no new orders, we've just cancelled existing ones (clearing TP/SL)
2305
+ const rollbackRequiresBuilderFee = [
2306
+ ...restorablePositionTpslOrders,
2307
+ ...restorableStandaloneTpslOrders,
2308
+ ].some((order) => order.chargesMetamaskBuilderFee);
2309
+ const requiresBuilderFee = replacementChargesMetamaskBuilderFee || rollbackRequiresBuilderFee;
2310
+ // Approval and builder-context resolution both finish before the
2311
+ // pre-cancel. A failure here therefore leaves the old protection intact.
2312
+ const builderFeeSetupContext = requiresBuilderFee
2313
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, {
2314
+ requiresBuilderFee: true,
2315
+ builderFeeApprovalFailureCode: perpsErrorCodes_js_1.PERPS_ERROR_CODES.TPSL_UPDATE_FAILED,
2316
+ })
2317
+ : await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
2318
+ const builderOrderContext = builderFeeSetupContext
2319
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this, builderFeeSetupContext)
2320
+ : undefined;
2321
+ const restoreCancelledProtection = async (cancelledOrderIds) => {
2322
+ const restoredOrderIds = [];
2323
+ let success = true;
2324
+ const protectionGroups = [
2325
+ {
2326
+ grouping: 'positionTpsl',
2327
+ entries: restorablePositionTpslOrders,
2328
+ },
2329
+ {
2330
+ grouping: 'na',
2331
+ entries: restorableStandaloneTpslOrders,
2332
+ },
2333
+ ];
2334
+ for (const protection of protectionGroups) {
2335
+ const entries = protection.entries.filter((entry) => cancelledOrderIds.has(entry.orderId));
2336
+ if (entries.length === 0) {
2337
+ continue;
2338
+ }
2339
+ try {
2340
+ const result = await exchangeClient.order({
2341
+ orders: entries.map((entry) => entry.order),
2342
+ grouping: protection.grouping,
2343
+ ...(entries.some((entry) => entry.chargesMetamaskBuilderFee) &&
2344
+ builderOrderContext && { builder: builderOrderContext }),
2345
+ });
2346
+ const statuses = result.response?.data?.statuses ?? [];
2347
+ const outcomes = statuses
2348
+ .slice(0, entries.length)
2349
+ .map((status) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderPlacementOutcome).call(this, status));
2350
+ restoredOrderIds.push(...outcomes.flatMap((outcome) => outcome ? [outcome.orderId] : []));
2351
+ if (result.status !== 'ok' ||
2352
+ statuses.length !== entries.length ||
2353
+ outcomes.some((outcome) => outcome === undefined)) {
2354
+ success = false;
2355
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").logger.error(new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.TPSL_UPDATE_FAILED), __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getErrorContext).call(this, 'updatePositionTPSL > restoreCancelledProtection', { symbol, grouping: protection.grouping, statuses }));
2356
+ }
2357
+ }
2358
+ catch (error) {
2359
+ success = false;
2360
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").logger.error((0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.updatePositionTPSL.restoreCancelledProtection'), __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getErrorContext).call(this, 'updatePositionTPSL > restoreCancelledProtection', { symbol, grouping: protection.grouping }));
2361
+ }
2362
+ }
2363
+ return { restoredOrderIds, success };
2364
+ };
2365
+ const createProtectionLostResult = (survivingOrderIds) => (0, hyperLiquidValidation_js_1.createErrorResult)(new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.TPSL_PROTECTION_LOST), {
2366
+ success: false,
2367
+ childOrderIds: [...new Set(survivingOrderIds)],
2368
+ });
2369
+ // Clearing has no replacement batch to preserve. A partial cancellation
2370
+ // is reported so the caller can retry the same clear operation.
1895
2371
  if (orders.length === 0) {
2372
+ const remainingOrderIds = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequests).call(this, exchangeClient, cancelRequests);
2373
+ if (remainingOrderIds.length > 0) {
2374
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.TPSL_UPDATE_FAILED);
2375
+ }
1896
2376
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('No new TP/SL orders to place - existing ones cancelled');
1897
2377
  return {
1898
2378
  success: true,
1899
2379
  // No orderId since we only cancelled orders, didn't place new ones
1900
2380
  };
1901
2381
  }
1902
- // Submit via SDK exchange client. Position-bound TP/SL uses 'positionTpsl';
1903
- // partial TP/SL must be standalone reduce-only triggers ('na'), since a
1904
- // position-bound TP/SL always closes the whole position.
1905
- const result = await exchangeClient.order({
1906
- orders,
1907
- grouping: isPartialTpsl ? 'na' : 'positionTpsl',
1908
- builder: await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this),
1909
- });
1910
- if (result.status !== 'ok') {
1911
- throw new Error(`TP/SL update failed: ${JSON.stringify(result)}`);
2382
+ // Cancel before placing for both position-bound and standalone partial
2383
+ // triggers. A place-first partial update leaves both trigger sets live
2384
+ // during the cancellation round trip and can reduce more than requested.
2385
+ const confirmedCancelledOldOrderIds = new Set();
2386
+ const oldCancellation = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequestBatch).call(this, exchangeClient, cancelRequests);
2387
+ if (!oldCancellation.responseComplete) {
2388
+ const requestedOrderIds = new Set(cancelRequests.map((request) => request.o));
2389
+ let possiblyLiveOrderIds = Array.from(requestedOrderIds, String);
2390
+ try {
2391
+ const liveOrders = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchOpenOrders).call(this, { dexName });
2392
+ possiblyLiveOrderIds = liveOrders
2393
+ .filter((order) => requestedOrderIds.has(order.oid))
2394
+ .map((order) => String(order.oid));
2395
+ }
2396
+ catch (error) {
2397
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Could not reconcile TP/SL protection after an incomplete cancel', {
2398
+ symbol,
2399
+ error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.updatePositionTPSL.reconcileProtection').message,
2400
+ });
2401
+ }
2402
+ return createProtectionLostResult(possiblyLiveOrderIds);
2403
+ }
2404
+ oldCancellation.cancelledOrderIds.forEach((orderId) => confirmedCancelledOldOrderIds.add(orderId));
2405
+ if (oldCancellation.remainingOrderIds.length > 0) {
2406
+ const restoration = await restoreCancelledProtection(confirmedCancelledOldOrderIds);
2407
+ if (!restoration.success) {
2408
+ return createProtectionLostResult([
2409
+ ...oldCancellation.remainingOrderIds.map(String),
2410
+ ...restoration.restoredOrderIds,
2411
+ ]);
2412
+ }
2413
+ const updateError = new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.TPSL_UPDATE_FAILED);
2414
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").logger.error(updateError, __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getErrorContext).call(this, 'updatePositionTPSL', {
2415
+ symbol: params.symbol,
2416
+ hasTakeProfit: params.takeProfitPrice !== undefined,
2417
+ hasStopLoss: params.stopLossPrice !== undefined,
2418
+ }));
2419
+ return (0, hyperLiquidValidation_js_1.createErrorResult)(updateError, {
2420
+ success: false,
2421
+ childOrderIds: [
2422
+ ...new Set([
2423
+ ...oldCancellation.remainingOrderIds.map(String),
2424
+ ...restoration.restoredOrderIds,
2425
+ ]),
2426
+ ],
2427
+ });
2428
+ }
2429
+ let result;
2430
+ try {
2431
+ result = await exchangeClient.order({
2432
+ orders,
2433
+ grouping: isPartialTpsl ? 'na' : 'positionTpsl',
2434
+ ...(replacementChargesMetamaskBuilderFee &&
2435
+ builderOrderContext && { builder: builderOrderContext }),
2436
+ });
2437
+ }
2438
+ catch (error) {
2439
+ const restoration = await restoreCancelledProtection(confirmedCancelledOldOrderIds);
2440
+ if (!restoration.success) {
2441
+ return createProtectionLostResult(restoration.restoredOrderIds);
2442
+ }
2443
+ throw error;
2444
+ }
2445
+ const placementStatuses = result.response?.data?.statuses ?? [];
2446
+ const placementOutcomes = placementStatuses
2447
+ .slice(0, orders.length)
2448
+ .map((status) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderPlacementOutcome).call(this, status));
2449
+ const replacementOrderIds = placementOutcomes.flatMap((outcome) => outcome ? [outcome.orderId] : []);
2450
+ const restingReplacementOrderIds = placementOutcomes.flatMap((outcome) => outcome?.state === 'resting' ? [outcome.orderId] : []);
2451
+ const filledReplacementOrderIds = placementOutcomes.flatMap((outcome) => outcome?.state === 'filled' ? [outcome.orderId] : []);
2452
+ if (result.status !== 'ok' ||
2453
+ placementStatuses.length !== orders.length ||
2454
+ replacementOrderIds.length !== orders.length) {
2455
+ const remainingReplacementIds = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequests).call(this, exchangeClient, restingReplacementOrderIds.map((orderId) => ({
2456
+ a: assetId,
2457
+ o: Number(orderId),
2458
+ })));
2459
+ const recoverableOrderIds = [
2460
+ ...filledReplacementOrderIds,
2461
+ ...remainingReplacementIds.map(String),
2462
+ ];
2463
+ if (recoverableOrderIds.length === 0) {
2464
+ const restoration = await restoreCancelledProtection(confirmedCancelledOldOrderIds);
2465
+ if (!restoration.success) {
2466
+ return createProtectionLostResult(restoration.restoredOrderIds);
2467
+ }
2468
+ }
2469
+ // A filled replacement may have changed or closed the position, while
2470
+ // an uncancelled replacement may still protect it. Restoring the old
2471
+ // whole-position triggers in either case risks stale or duplicate
2472
+ // protection, so return those IDs for caller reconciliation instead.
2473
+ if (recoverableOrderIds.length > 0) {
2474
+ if (!isPartialTpsl) {
2475
+ return createProtectionLostResult(recoverableOrderIds);
2476
+ }
2477
+ return (0, hyperLiquidValidation_js_1.createErrorResult)(new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.TPSL_UPDATE_FAILED), {
2478
+ success: false,
2479
+ childOrderIds: recoverableOrderIds,
2480
+ });
2481
+ }
2482
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.TPSL_UPDATE_FAILED);
1912
2483
  }
1913
2484
  return {
1914
2485
  success: true,
@@ -1936,8 +2507,9 @@ class HyperLiquidProvider {
1936
2507
  async closePosition(params) {
1937
2508
  try {
1938
2509
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Closing position:', params);
1939
- // Ensure provider is ready for trading (includes signing operations)
1940
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
2510
+ // The delegated placeOrder call resolves the builder-fee policy for the
2511
+ // concrete close order after validation.
2512
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
1941
2513
  // Use provided position (from WebSocket) or fetch from cache
1942
2514
  // This avoids unnecessary API calls and prevents 429 rate limiting
1943
2515
  let { position } = params;
@@ -2737,6 +3309,7 @@ class HyperLiquidProvider {
2737
3309
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Using cached open orders from WebSocket', {
2738
3310
  count: cachedOrders.length,
2739
3311
  });
3312
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_restoreScaleOrderGroups).call(this, cachedOrders);
2740
3313
  return cachedOrders;
2741
3314
  }
2742
3315
  }
@@ -2767,6 +3340,7 @@ class HyperLiquidProvider {
2767
3340
  const position = positions.find((pos) => pos.symbol === order.coin);
2768
3341
  return (0, hyperLiquidAdapter_js_1.adaptOrderFromSDK)(order, position);
2769
3342
  });
3343
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_restoreScaleOrderGroups).call(this, orders);
2770
3344
  return orders;
2771
3345
  }
2772
3346
  catch (error) {
@@ -3320,6 +3894,7 @@ class HyperLiquidProvider {
3320
3894
  * @returns A promise that resolves to the combined market data from all enabled DEXs.
3321
3895
  */
3322
3896
  async getMarketDataWithPrices() {
3897
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
3323
3898
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Getting market data with prices via HyperLiquid SDK');
3324
3899
  // Ensure asset mapping is built first (populates meta cache)
3325
3900
  // This guarantees buildAssetMapping has run before we check cache,
@@ -3335,16 +3910,15 @@ class HyperLiquidProvider {
3335
3910
  // Check the meta cache first to avoid redundant API calls when buildAssetMapping
3336
3911
  // has already populated it; on cache miss, delegate to #fetchSingleDexFresh.
3337
3912
  const dexDataResults = await Promise.all(enabledDexs.map(async (dex) => {
3338
- const dexKey = dex ?? '';
3339
3913
  const dexParam = dex ?? '';
3340
- const cachedMeta = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dexKey);
3914
+ const cachedMeta = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dex);
3341
3915
  if (!cachedMeta) {
3342
3916
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`[getMarketDataWithPrices] Cache miss for ${dex ?? 'main'}, fetching`);
3343
- return __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchSingleDexFresh).call(this, infoClient, dex);
3917
+ return __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchSingleDexFresh).call(this, infoClient, dex, lifecycleGeneration);
3344
3918
  }
3345
3919
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`[getMarketDataWithPrices] Using cached meta for ${dex ?? 'main'}`, { universeSize: cachedMeta.universe.length });
3346
3920
  let metaForDex = cachedMeta;
3347
- let assetCtxs = __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").getDexAssetCtxsCache(dexKey) ?? [];
3921
+ let assetCtxs = __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").getDexAssetCtxsCache(dexParam) ?? [];
3348
3922
  let failedStep;
3349
3923
  let errorMessage;
3350
3924
  if (assetCtxs.length !== metaForDex.universe.length) {
@@ -3365,10 +3939,12 @@ class HyperLiquidProvider {
3365
3939
  }
3366
3940
  metaForDex = freshMeta;
3367
3941
  assetCtxs = freshAssetCtxs;
3368
- __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").set(dexKey, freshMeta);
3369
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexMetaCache(dexKey, freshMeta);
3370
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexAssetCtxsCache(dexKey, assetCtxs);
3371
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dex, freshMeta);
3942
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheDexMetadataFromRead).call(this, {
3943
+ dex,
3944
+ meta: freshMeta,
3945
+ assetCtxs,
3946
+ lifecycleGeneration,
3947
+ });
3372
3948
  }
3373
3949
  catch (error) {
3374
3950
  return {
@@ -3420,7 +3996,7 @@ class HyperLiquidProvider {
3420
3996
  const retryDelayMs = 2000;
3421
3997
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`[getMarketDataWithPrices] All DEXs returned empty, retrying in ${retryDelayMs}ms`);
3422
3998
  await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
3423
- const retryResults = await Promise.all(enabledDexs.map((dex) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchSingleDexFresh).call(this, infoClient, dex)));
3999
+ const retryResults = await Promise.all(enabledDexs.map((dex) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchSingleDexFresh).call(this, infoClient, dex, lifecycleGeneration)));
3424
4000
  const usdcFilteredRetryResults = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_excludeNonUsdcCollateralResults).call(this, retryResults);
3425
4001
  __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mergeDexResultsInto).call(this, usdcFilteredRetryResults, combinedUniverse, combinedAssetCtxs, combinedAllMids);
3426
4002
  latestDexResults = usdcFilteredRetryResults;
@@ -3480,6 +4056,9 @@ class HyperLiquidProvider {
3480
4056
  assetCtxs: combinedAssetCtxs,
3481
4057
  allMids: combinedAllMids,
3482
4058
  }, __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").marketDataFormatters, hyperLiquidConfig_js_1.HIP3_ASSET_MARKET_TYPES, hyperLiquidConfig_js_1.HYPERLIQUID_ASSET_NAMES);
4059
+ if (!__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Market data cache write')) {
4060
+ return transformedMarketData;
4061
+ }
3483
4062
  return __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheFreshMarketDataSnapshot).call(this, transformedMarketData, latestDexResults);
3484
4063
  }
3485
4064
  /**
@@ -4134,23 +4713,28 @@ class HyperLiquidProvider {
4134
4713
  async toggleTestnet() {
4135
4714
  try {
4136
4715
  const newIsTestnet = !__classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode();
4137
- // Await pending initialization to prevent race condition where
4138
- // the IIFE sets clientsInitialized = true after we reset it
4139
- const pendingInit = __classPrivateFieldGet(this, _HyperLiquidProvider_initializationPromise, "f");
4140
- __classPrivateFieldSet(this, _HyperLiquidProvider_initializationPromise, null, "f");
4141
- if (pendingInit) {
4142
- try {
4143
- await pendingInit;
4144
- }
4145
- catch {
4146
- // Ignore - we're switching networks anyway
4147
- }
4716
+ const disconnectResult = await this.disconnect();
4717
+ if (!disconnectResult.success) {
4718
+ // The network did not change. Keep the provider eligible for lazy
4719
+ // recovery instead of leaving this aborted toggle permanently sticky.
4720
+ __classPrivateFieldSet(this, _HyperLiquidProvider_isDisconnected, false, "f");
4721
+ throw new Error(disconnectResult.error ??
4722
+ 'Failed to disconnect before network toggle');
4148
4723
  }
4149
4724
  // Update all services
4150
4725
  __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").setTestnetMode(newIsTestnet);
4726
+ // Invalidate reads that started after disconnect completed but before
4727
+ // the clients switched networks.
4728
+ __classPrivateFieldSet(this, _HyperLiquidProvider_lifecycleGeneration, __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f") + 1, "f");
4151
4729
  __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").setTestnetMode(newIsTestnet);
4152
- // Reset initialization flag so clients will be recreated on next use
4153
- __classPrivateFieldSet(this, _HyperLiquidProvider_clientsInitialized, false, "f");
4730
+ const initializeResult = await this.initialize();
4731
+ if (!initializeResult.success) {
4732
+ // The network switch is already committed. Leave the provider eligible
4733
+ // for lazy initialization so the next operation can retry the failed
4734
+ // client setup instead of remaining permanently disconnected.
4735
+ __classPrivateFieldSet(this, _HyperLiquidProvider_isDisconnected, false, "f");
4736
+ throw new Error(initializeResult.error ?? 'Failed to initialize after network toggle');
4737
+ }
4154
4738
  return {
4155
4739
  success: true,
4156
4740
  isTestnet: newIsTestnet,
@@ -4169,9 +4753,18 @@ class HyperLiquidProvider {
4169
4753
  * @returns A promise that resolves to the result.
4170
4754
  */
4171
4755
  async initialize() {
4756
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
4172
4757
  try {
4758
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0) {
4759
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
4760
+ }
4173
4761
  // Ensure clients are initialized (lazy initialization)
4174
4762
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureClientsInitialized).call(this);
4763
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0 ||
4764
+ lifecycleGeneration !== __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f")) {
4765
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
4766
+ }
4767
+ __classPrivateFieldSet(this, _HyperLiquidProvider_isDisconnected, false, "f");
4175
4768
  return {
4176
4769
  success: true,
4177
4770
  chainId: (0, hyperLiquidConfig_js_1.getChainId)(__classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode()),
@@ -4309,6 +4902,7 @@ class HyperLiquidProvider {
4309
4902
  * @returns A promise that resolves to the numeric result.
4310
4903
  */
4311
4904
  async getMaxLeverage(asset) {
4905
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
4312
4906
  try {
4313
4907
  // Check cache first
4314
4908
  const cached = __classPrivateFieldGet(this, _HyperLiquidProvider_maxLeverageCache, "f").get(asset);
@@ -4341,11 +4935,12 @@ class HyperLiquidProvider {
4341
4935
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`Asset ${asset} not found in universe, using default max leverage`);
4342
4936
  return perpsConfig_js_1.PERPS_CONSTANTS.DefaultMaxLeverage;
4343
4937
  }
4344
- // Cache the result
4345
- __classPrivateFieldGet(this, _HyperLiquidProvider_maxLeverageCache, "f").set(asset, {
4346
- value: assetInfo.maxLeverage,
4347
- timestamp: now,
4348
- });
4938
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Maximum leverage cache write')) {
4939
+ __classPrivateFieldGet(this, _HyperLiquidProvider_maxLeverageCache, "f").set(asset, {
4940
+ value: assetInfo.maxLeverage,
4941
+ timestamp: now,
4942
+ });
4943
+ }
4349
4944
  return assetInfo.maxLeverage;
4350
4945
  }
4351
4946
  catch (error) {
@@ -4366,7 +4961,13 @@ class HyperLiquidProvider {
4366
4961
  * @returns A promise that resolves to the result.
4367
4962
  */
4368
4963
  async calculateFees(params) {
4964
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
4369
4965
  const { orderType, isMaker = false, amount, symbol } = params;
4966
+ const numericAmount = amount === undefined ? undefined : Number.parseFloat(amount);
4967
+ const parsedAmount = numericAmount === undefined ||
4968
+ (Number.isFinite(numericAmount) && numericAmount >= 0)
4969
+ ? numericAmount
4970
+ : 0;
4370
4971
  // Every placement is charged as its execution kind: a stop_market fills as a
4371
4972
  // market order (taker), a stop_limit as a limit order, a scale ladder as the
4372
4973
  // resting limit orders it fans out into, and a TWAP as the marketable
@@ -4413,6 +5014,7 @@ class HyperLiquidProvider {
4413
5014
  // Try to get user-specific rates if wallet is connected
4414
5015
  try {
4415
5016
  const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
5017
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'User fee cache read');
4416
5018
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('User Address Retrieved', {
4417
5019
  userAddress,
4418
5020
  network: __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode() ? 'testnet' : 'mainnet',
@@ -4453,7 +5055,9 @@ class HyperLiquidProvider {
4453
5055
  });
4454
5056
  // Fetch fresh rates from SDK
4455
5057
  // Read-only operation: only need client initialization
5058
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'User fee fetch');
4456
5059
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureClientsInitialized).call(this);
5060
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'User fee fetch');
4457
5061
  __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").ensureInitialized();
4458
5062
  const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient();
4459
5063
  const userFees = await infoClient.userFees({
@@ -4466,13 +5070,21 @@ class HyperLiquidProvider {
4466
5070
  activeStakingDiscount: userFees.activeStakingDiscount,
4467
5071
  });
4468
5072
  // Parse base user rates (these don't include discounts as expected)
4469
- const baseUserTakerRate = parseFloat(userFees.userCrossRate);
4470
- const baseUserMakerRate = parseFloat(userFees.userAddRate);
4471
- const baseUserSpotTakerRate = parseFloat(userFees.userSpotCrossRate);
4472
- const baseUserSpotMakerRate = parseFloat(userFees.userSpotAddRate);
5073
+ const baseUserTakerRate = (0, stringParseUtils_js_1.parseBoundedNonNegativeDecimal)(userFees.userCrossRate, MAX_API_FEE_RATE);
5074
+ const baseUserMakerRate = (0, stringParseUtils_js_1.parseBoundedNonNegativeDecimal)(userFees.userAddRate, MAX_API_FEE_RATE);
5075
+ const baseUserSpotTakerRate = (0, stringParseUtils_js_1.parseBoundedNonNegativeDecimal)(userFees.userSpotCrossRate, MAX_API_FEE_RATE);
5076
+ const baseUserSpotMakerRate = (0, stringParseUtils_js_1.parseBoundedNonNegativeDecimal)(userFees.userSpotAddRate, MAX_API_FEE_RATE);
4473
5077
  // Apply discounts manually since HyperLiquid API doesn't apply them
4474
- const referralDiscount = parseFloat(userFees.activeReferralDiscount || '0');
4475
- const stakingDiscount = parseFloat(userFees.activeStakingDiscount?.discount || '0');
5078
+ const referralDiscount = (0, stringParseUtils_js_1.parseBoundedNonNegativeDecimal)(userFees.activeReferralDiscount || '0', MAX_API_FEE_DISCOUNT);
5079
+ const stakingDiscount = (0, stringParseUtils_js_1.parseBoundedNonNegativeDecimal)(userFees.activeStakingDiscount?.discount || '0', MAX_API_FEE_DISCOUNT);
5080
+ if (baseUserTakerRate === null ||
5081
+ baseUserMakerRate === null ||
5082
+ baseUserSpotTakerRate === null ||
5083
+ baseUserSpotMakerRate === null ||
5084
+ referralDiscount === null ||
5085
+ stakingDiscount === null) {
5086
+ throw new Error('Invalid fee rates received from API');
5087
+ }
4476
5088
  // Calculate total discount (referral + staking, but not compounding)
4477
5089
  const totalDiscount = Math.min(referralDiscount + stakingDiscount, 0.4); // Cap at 40%
4478
5090
  // Apply discount to rates
@@ -4498,20 +5110,20 @@ class HyperLiquidProvider {
4498
5110
  },
4499
5111
  });
4500
5112
  // Validate all rates are valid numbers before caching
4501
- if (isNaN(perpsTakerRate) ||
4502
- isNaN(perpsMakerRate) ||
4503
- isNaN(spotTakerRate) ||
4504
- isNaN(spotMakerRate) ||
5113
+ if (!Number.isFinite(perpsTakerRate) ||
5114
+ !Number.isFinite(perpsMakerRate) ||
5115
+ !Number.isFinite(spotTakerRate) ||
5116
+ !Number.isFinite(spotMakerRate) ||
4505
5117
  perpsTakerRate < 0 ||
4506
5118
  perpsMakerRate < 0 ||
4507
5119
  spotTakerRate < 0 ||
4508
5120
  spotMakerRate < 0) {
4509
5121
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Fee Rate Validation Failed', {
4510
5122
  validation: {
4511
- perpsTakerValid: !isNaN(perpsTakerRate) && perpsTakerRate >= 0,
4512
- perpsMakerValid: !isNaN(perpsMakerRate) && perpsMakerRate >= 0,
4513
- spotTakerValid: !isNaN(spotTakerRate) && spotTakerRate >= 0,
4514
- spotMakerValid: !isNaN(spotMakerRate) && spotMakerRate >= 0,
5123
+ perpsTakerValid: Number.isFinite(perpsTakerRate) && perpsTakerRate >= 0,
5124
+ perpsMakerValid: Number.isFinite(perpsMakerRate) && perpsMakerRate >= 0,
5125
+ spotTakerValid: Number.isFinite(spotTakerRate) && spotTakerRate >= 0,
5126
+ spotMakerValid: Number.isFinite(spotMakerRate) && spotMakerRate >= 0,
4515
5127
  },
4516
5128
  rawValues: {
4517
5129
  perpsTakerRate,
@@ -4530,6 +5142,7 @@ class HyperLiquidProvider {
4530
5142
  timestamp: Date.now(),
4531
5143
  ttl: 5 * 60 * 1000, // 5 minutes
4532
5144
  };
5145
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'User fee cache write');
4533
5146
  __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeCache, "f").set(userAddress, rates);
4534
5147
  // Same maker/taker decision as the base rates above, chase included.
4535
5148
  let userFeeRate = chargesMakerRate
@@ -4553,6 +5166,9 @@ class HyperLiquidProvider {
4553
5166
  catch (error) {
4554
5167
  // Silently fall back to base rates
4555
5168
  const safeError = (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.getFeeSchedule');
5169
+ if (safeError.message === perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE) {
5170
+ throw safeError;
5171
+ }
4556
5172
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Fee API Call Failed - Falling Back to Base Rates', {
4557
5173
  error: safeError.message,
4558
5174
  errorType: safeError.name,
@@ -4561,43 +5177,46 @@ class HyperLiquidProvider {
4561
5177
  userAddress: 'unknown',
4562
5178
  });
4563
5179
  }
4564
- const parsedAmount = amount ? parseFloat(amount) : 0;
5180
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Fee calculation');
4565
5181
  // Protocol base fee (HyperLiquid's fee)
4566
5182
  const protocolFeeRate = feeRate;
4567
- let protocolFeeAmount;
4568
- if (amount === undefined) {
4569
- protocolFeeAmount = undefined;
4570
- }
4571
- else if (isNaN(parsedAmount)) {
4572
- protocolFeeAmount = 0;
4573
- }
4574
- else {
4575
- protocolFeeAmount = parsedAmount * protocolFeeRate;
4576
- }
4577
- // MetaMask builder fee (0.1% = 0.001) with optional reward discount
4578
- let metamaskFeeRate = hyperLiquidConfig_js_1.BUILDER_FEE_CONFIG.MaxFeeDecimal;
4579
- // Apply MetaMask reward discount if active
4580
- if (__classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") !== undefined) {
4581
- const discount = __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") / hyperLiquidConfig_js_1.BASIS_POINTS_DIVISOR; // Convert basis points to decimal
4582
- metamaskFeeRate = hyperLiquidConfig_js_1.BUILDER_FEE_CONFIG.MaxFeeDecimal * (1 - discount);
5183
+ const protocolFeeAmount = parsedAmount === undefined ? undefined : parsedAmount * protocolFeeRate;
5184
+ // The provider policy, not the client, owns whether this placement can
5185
+ // carry a builder fee. The dedicated TWAP action has no builder field.
5186
+ const { chargesMetamaskBuilderFee } = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, params);
5187
+ const baseMetamaskFeeRate = chargesMetamaskBuilderFee
5188
+ ? hyperLiquidConfig_js_1.BUILDER_FEE_CONFIG.MaxFeeDecimal
5189
+ : 0;
5190
+ const metamaskFeeDiscount = __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") === undefined
5191
+ ? 0
5192
+ : __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") / hyperLiquidConfig_js_1.BASIS_POINTS_DIVISOR;
5193
+ const metamaskFeeRate = baseMetamaskFeeRate * (1 - metamaskFeeDiscount);
5194
+ if (chargesMetamaskBuilderFee && __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") !== undefined) {
4583
5195
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Applied MetaMask fee discount', {
4584
- originalRate: hyperLiquidConfig_js_1.BUILDER_FEE_CONFIG.MaxFeeDecimal,
5196
+ originalRate: baseMetamaskFeeRate,
4585
5197
  discountBips: __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f"),
4586
5198
  discountPercentage: __classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") / 100,
4587
5199
  adjustedRate: metamaskFeeRate,
4588
- discountAmount: hyperLiquidConfig_js_1.BUILDER_FEE_CONFIG.MaxFeeDecimal * discount,
5200
+ discountAmount: baseMetamaskFeeRate * metamaskFeeDiscount,
4589
5201
  });
4590
5202
  }
4591
- const validAmountForMetamaskFee = isNaN(parsedAmount)
4592
- ? 0
4593
- : parsedAmount * metamaskFeeRate;
4594
- const metamaskFeeAmount = amount === undefined ? undefined : validAmountForMetamaskFee;
5203
+ const metamaskFeeAmount = parsedAmount === undefined ? undefined : parsedAmount * metamaskFeeRate;
4595
5204
  // Total fees
4596
5205
  const totalFeeRate = protocolFeeRate + metamaskFeeRate;
4597
- const validAmountForTotalFee = isNaN(parsedAmount)
4598
- ? 0
4599
- : parsedAmount * totalFeeRate;
4600
- const totalFeeAmount = amount === undefined ? undefined : validAmountForTotalFee;
5206
+ const totalFeeAmount = parsedAmount === undefined ? undefined : parsedAmount * totalFeeRate;
5207
+ if (!Number.isFinite(protocolFeeRate) ||
5208
+ protocolFeeRate < 0 ||
5209
+ !Number.isFinite(metamaskFeeRate) ||
5210
+ metamaskFeeRate < 0 ||
5211
+ !Number.isFinite(totalFeeRate) ||
5212
+ totalFeeRate < 0 ||
5213
+ (protocolFeeAmount !== undefined &&
5214
+ !Number.isFinite(protocolFeeAmount)) ||
5215
+ (metamaskFeeAmount !== undefined &&
5216
+ !Number.isFinite(metamaskFeeAmount)) ||
5217
+ (totalFeeAmount !== undefined && !Number.isFinite(totalFeeAmount))) {
5218
+ throw new Error('Invalid fee calculation result');
5219
+ }
4601
5220
  const result = {
4602
5221
  // Total fees
4603
5222
  feeRate: totalFeeRate,
@@ -4656,100 +5275,18 @@ class HyperLiquidProvider {
4656
5275
  * @returns A promise that resolves to the result.
4657
5276
  */
4658
5277
  async disconnect() {
4659
- __classPrivateFieldSet(this, _HyperLiquidProvider_chasePlacementBlockers, __classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") + 1, "f");
4660
- __classPrivateFieldSet(this, _HyperLiquidProvider_chaseGeneration, __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f") + 1, "f");
5278
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationPromise, "f")) {
5279
+ return __classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationPromise, "f");
5280
+ }
5281
+ const operation = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_performDisconnect).call(this);
5282
+ __classPrivateFieldSet(this, _HyperLiquidProvider_disconnectOperationPromise, operation, "f");
4661
5283
  try {
4662
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Disconnecting provider', {
4663
- isTestnet: __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode(),
4664
- timestamp: new Date().toISOString(),
4665
- });
4666
- // Clear subscriptions through subscription service
4667
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").clearAll();
4668
- // Stop every chase loop: each holds a pending timer, and re-pricing after
4669
- // a disconnect would sign orders against a client that is being torn down.
4670
- // Orders already resting are deliberately left alone — disconnecting is
4671
- // not a request to cancel the user's positions or orders.
4672
- // Invalidate placements first, then drain every mutation before clearing
4673
- // the registry and tearing down the clients they use.
4674
- for (const [sessionId, session] of __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").entries()) {
4675
- if (session.active) {
4676
- __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_stopChaseSession).call(this, sessionId);
4677
- }
4678
- }
4679
- await Promise.all([...__classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementWaiters, "f")]);
4680
- await __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f");
4681
- await Promise.allSettled([...__classPrivateFieldGet(this, _HyperLiquidProvider_chaseTerminations, "f").values()]);
4682
- __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").clear();
4683
- __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTerminations, "f").clear();
4684
- __classPrivateFieldGet(this, _HyperLiquidProvider_terminatedChaseHandles, "f").clear();
4685
- __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").clear();
4686
- // Clear fee cache
4687
- this.clearFeeCache();
4688
- // Clear session caches (ensures fresh state on reconnect/account switch)
4689
- __classPrivateFieldGet(this, _HyperLiquidProvider_referralCheckCache, "f").clear();
4690
- __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").clear();
4691
- __classPrivateFieldSet(this, _HyperLiquidProvider_subscriptionBuilderApprovalEpoch, __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionBuilderApprovalEpoch, "f") + 1, "f");
4692
- __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").clear();
4693
- __classPrivateFieldSet(this, _HyperLiquidProvider_userFeeResolution, undefined, "f");
4694
- __classPrivateFieldSet(this, _HyperLiquidProvider_userFeeDiscountBips, undefined, "f");
4695
- // NOTE: UnifiedAccountCache is global and NOT cleared on disconnect
4696
- // to prevent repeated signing requests across reconnections
4697
- __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").clear();
4698
- __classPrivateFieldSet(this, _HyperLiquidProvider_cachedSpotMeta, null, "f");
4699
- __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").reset();
4700
- __classPrivateFieldSet(this, _HyperLiquidProvider_dexDiscoveryComplete, false, "f");
4701
- // Await pending initialization before clearing to prevent the IIFE from
4702
- // setting clientsInitialized = true after disconnect completes
4703
- const pendingInit = __classPrivateFieldGet(this, _HyperLiquidProvider_initializationPromise, "f");
4704
- const pendingReady = __classPrivateFieldGet(this, _HyperLiquidProvider_ensureReadyPromise, "f");
4705
- const pendingTradingSetup = __classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f");
4706
- // Clear references first to prevent new callers from reusing
4707
- __classPrivateFieldSet(this, _HyperLiquidProvider_initializationPromise, null, "f");
4708
- __classPrivateFieldSet(this, _HyperLiquidProvider_ensureReadyPromise, null, "f");
4709
- __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, null, "f");
4710
- __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupComplete, false, "f");
4711
- __classPrivateFieldGet(this, _HyperLiquidProvider_pendingBuilderFeeApprovals, "f").clear();
4712
- // Wait for pending operations to complete (ignore errors)
4713
- // This prevents IIFEs from setting state after disconnect completes
4714
- if (pendingInit) {
4715
- try {
4716
- await pendingInit;
4717
- }
4718
- catch {
4719
- // Ignore - we're disconnecting anyway
4720
- }
4721
- }
4722
- if (pendingReady) {
4723
- try {
4724
- await pendingReady;
4725
- }
4726
- catch {
4727
- // Ignore - we're disconnecting anyway
4728
- }
4729
- }
4730
- if (pendingTradingSetup) {
4731
- try {
4732
- await pendingTradingSetup;
4733
- }
4734
- catch {
4735
- // Ignore - we're disconnecting anyway
4736
- }
4737
- }
4738
- // Reset client initialization flag so wallet adapter will be recreated with new account
4739
- // This fixes account synchronization issue where old account's address persists in wallet adapter
4740
- __classPrivateFieldSet(this, _HyperLiquidProvider_clientsInitialized, false, "f");
4741
- // Disconnect client service
4742
- await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").disconnect();
4743
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Provider fully disconnected', {
4744
- timestamp: new Date().toISOString(),
4745
- });
4746
- return { success: true };
4747
- }
4748
- catch (error) {
4749
- return (0, hyperLiquidValidation_js_1.createErrorResult)(error, { success: false });
5284
+ return await operation;
4750
5285
  }
4751
5286
  finally {
4752
- __classPrivateFieldSet(this, _HyperLiquidProvider_chasePlacementBlockers, __classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") - 1, "f");
5287
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationPromise, "f") === operation) {
5288
+ __classPrivateFieldSet(this, _HyperLiquidProvider_disconnectOperationPromise, null, "f");
5289
+ }
4753
5290
  }
4754
5291
  }
4755
5292
  /**
@@ -4876,7 +5413,75 @@ class HyperLiquidProvider {
4876
5413
  }
4877
5414
  }
4878
5415
  exports.HyperLiquidProvider = HyperLiquidProvider;
4879
- _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) {
5416
+ _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) {
5417
+ const fallbackPolicy = Object.prototype.hasOwnProperty.call(HYPERLIQUID_ORDER_FEE_CONFIG, params.orderType)
5418
+ ? HYPERLIQUID_ORDER_FEE_CONFIG[params.orderType]
5419
+ : undefined;
5420
+ if (fallbackPolicy) {
5421
+ return fallbackPolicy;
5422
+ }
5423
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Unknown order type used the safe builder-fee policy', { orderType: params.orderType });
5424
+ return HYPERLIQUID_ORDER_FEE_CONFIG.market;
5425
+ }, _HyperLiquidProvider_assertProviderLifecycleCurrent = function _HyperLiquidProvider_assertProviderLifecycleCurrent(generation, operation) {
5426
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_isDisconnected, "f") || generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f")) {
5427
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Provider operation became stale during disconnect', { operation });
5428
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5429
+ }
5430
+ }, _HyperLiquidProvider_assertCacheWriteLifecycleCurrent = function _HyperLiquidProvider_assertCacheWriteLifecycleCurrent(generation, operation) {
5431
+ if (!__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, generation, operation)) {
5432
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5433
+ }
5434
+ }, _HyperLiquidProvider_isCacheWriteLifecycleCurrent = function _HyperLiquidProvider_isCacheWriteLifecycleCurrent(generation, operation) {
5435
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0 ||
5436
+ generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f")) {
5437
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Cache write became stale during disconnect', { operation });
5438
+ return false;
5439
+ }
5440
+ return true;
5441
+ }, _HyperLiquidProvider_getFreshOrderCapabilityMarkets =
5442
+ /**
5443
+ * Return filtered market metadata fresh enough for a capability query.
5444
+ * Each DEX shares one refresh across concurrent callers, and a failed
5445
+ * refresh remains immediately retryable.
5446
+ *
5447
+ * @param dex - Routed DEX, or null for the main DEX.
5448
+ * @returns Fresh filtered markets for that route.
5449
+ */
5450
+ async function _HyperLiquidProvider_getFreshOrderCapabilityMarkets(dex) {
5451
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5452
+ const cachedMarkets = __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesMarketsByDex, "f").get(dex);
5453
+ if (cachedMarkets &&
5454
+ Date.now() - cachedMarkets.freshAt <
5455
+ perpsConfig_js_1.PERFORMANCE_CONFIG.OrderCapabilitiesMetaFreshnessMs) {
5456
+ return cachedMarkets.markets;
5457
+ }
5458
+ let refreshPromise = __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesRefreshByDex, "f").get(dex);
5459
+ if (!refreshPromise) {
5460
+ refreshPromise = (async () => {
5461
+ const markets = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchMarketsForDex).call(this, {
5462
+ dex,
5463
+ skipCache: true,
5464
+ });
5465
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Order capability metadata refresh');
5466
+ __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesMarketsByDex, "f").set(dex, {
5467
+ markets,
5468
+ freshAt: Date.now(),
5469
+ });
5470
+ return markets;
5471
+ })();
5472
+ __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesRefreshByDex, "f").set(dex, refreshPromise);
5473
+ }
5474
+ try {
5475
+ const markets = await refreshPromise;
5476
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Order capability read');
5477
+ return markets;
5478
+ }
5479
+ finally {
5480
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesRefreshByDex, "f").get(dex) === refreshPromise) {
5481
+ __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesRefreshByDex, "f").delete(dex);
5482
+ }
5483
+ }
5484
+ }, _HyperLiquidProvider_compilePatternsSafely = function _HyperLiquidProvider_compilePatternsSafely(patterns, listName) {
4880
5485
  const compiled = [];
4881
5486
  for (const pattern of patterns) {
4882
5487
  try {
@@ -4899,6 +5504,9 @@ _HyperLiquidProvider_deps = new WeakMap(), _HyperLiquidProvider_clientService =
4899
5504
  * the connection is fully established before marking initialization complete.
4900
5505
  */
4901
5506
  async function _HyperLiquidProvider_ensureClientsInitialized() {
5507
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0) {
5508
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5509
+ }
4902
5510
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_clientsInitialized, "f")) {
4903
5511
  return; // Already initialized
4904
5512
  }
@@ -4906,6 +5514,9 @@ async function _HyperLiquidProvider_ensureClientsInitialized() {
4906
5514
  // This prevents race conditions when multiple methods call concurrently
4907
5515
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_initializationPromise, "f")) {
4908
5516
  await __classPrivateFieldGet(this, _HyperLiquidProvider_initializationPromise, "f");
5517
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0) {
5518
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5519
+ }
4909
5520
  return;
4910
5521
  }
4911
5522
  // Create and cache the initialization promise
@@ -4914,8 +5525,14 @@ async function _HyperLiquidProvider_ensureClientsInitialized() {
4914
5525
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_clientsInitialized, "f")) {
4915
5526
  return;
4916
5527
  }
5528
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0) {
5529
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5530
+ }
4917
5531
  const wallet = __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").createWalletAdapter();
4918
5532
  await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").initialize(wallet);
5533
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") > 0) {
5534
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
5535
+ }
4919
5536
  // Set termination callback for logging when WebSocket terminates
4920
5537
  // Note: Do NOT restore subscriptions here - termination means connection failed permanently
4921
5538
  __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").setOnTerminateCallback((error) => {
@@ -5386,7 +6003,13 @@ async function _HyperLiquidProvider_ensureReady() {
5386
6003
  // Await initialization - keep the promise so subsequent calls resolve immediately
5387
6004
  // The promise is only reset in disconnect() for clean reconnection,
5388
6005
  // or when DEX discovery was degraded so the next caller retries.
5389
- await __classPrivateFieldGet(this, _HyperLiquidProvider_ensureReadyPromise, "f");
6006
+ try {
6007
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_ensureReadyPromise, "f");
6008
+ }
6009
+ catch (error) {
6010
+ __classPrivateFieldSet(this, _HyperLiquidProvider_ensureReadyPromise, null, "f");
6011
+ throw error;
6012
+ }
5390
6013
  if (!__classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryComplete, "f")) {
5391
6014
  // DEX discovery failed transiently — reset so next call retries.
5392
6015
  // Trading still works (main DEX mapping is populated), but HIP-3 markets
@@ -5401,72 +6024,102 @@ async function _HyperLiquidProvider_ensureReady() {
5401
6024
  __classPrivateFieldSet(this, _HyperLiquidProvider_ensureReadyPromise, null, "f");
5402
6025
  }
5403
6026
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReady] Initialization complete');
5404
- }, _HyperLiquidProvider_ensureReadyForTrading = async function _HyperLiquidProvider_ensureReadyForTrading() {
6027
+ }, _HyperLiquidProvider_ensureBuilderFeeSetup =
6028
+ /**
6029
+ * Approve the standard builder fee once for the current account and network.
6030
+ * TWAP never calls this because its native action has no builder field.
6031
+ * Without an approval failure code, approval remains non-blocking and the
6032
+ * returned context describes attribution only; it does not prove approval.
6033
+ *
6034
+ * @param approvalFailureCode - Operation-specific error to throw when
6035
+ * approval is unavailable or fails.
6036
+ * @returns The account, network, and configured builder for the action.
6037
+ */
6038
+ async function _HyperLiquidProvider_ensureBuilderFeeSetup(approvalFailureCode) {
6039
+ const isTestnet = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode();
6040
+ const network = isTestnet ? 'testnet' : 'mainnet';
6041
+ const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
6042
+ const cacheKey = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCacheKey).call(this, network, userAddress);
6043
+ const builderAddress = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderAddress).call(this, isTestnet);
6044
+ const context = {
6045
+ network,
6046
+ userAddress,
6047
+ builderAddress,
6048
+ };
6049
+ const setupKey = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getApprovedBuilderKey).call(this, network, userAddress, builderAddress);
6050
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").has(cacheKey)) {
6051
+ return context;
6052
+ }
6053
+ let pendingApproval = __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").get(setupKey);
6054
+ if (!pendingApproval) {
6055
+ pendingApproval = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureBuilderFeeApproval).call(this, context);
6056
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").set(setupKey, pendingApproval);
6057
+ }
6058
+ try {
6059
+ await pendingApproval;
6060
+ }
6061
+ catch (error) {
6062
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureBuilderFeeSetup] Builder fee approval failed', error);
6063
+ if (approvalFailureCode) {
6064
+ throw new Error(approvalFailureCode);
6065
+ }
6066
+ }
6067
+ finally {
6068
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").get(setupKey) === pendingApproval) {
6069
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").delete(setupKey);
6070
+ }
6071
+ }
6072
+ if (approvalFailureCode && !__classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").has(cacheKey)) {
6073
+ throw new Error(approvalFailureCode);
6074
+ }
6075
+ return context;
6076
+ }, _HyperLiquidProvider_ensureReadyForTrading = async function _HyperLiquidProvider_ensureReadyForTrading(options) {
5405
6077
  // First ensure basic initialization is complete
5406
6078
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReady).call(this);
5407
6079
  // dexAbstraction users were deferred during init to avoid an EIP-712 prompt
5408
6080
  // on Perps section open. Drive the migration here, gated by its own cache so
5409
6081
  // already-migrated or already-rejected users are not re-prompted.
5410
6082
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureUnifiedAccountEnabled).call(this, { allowUserSigning: true });
5411
- // If trading setup already complete, only retry builder fee if it previously failed
5412
- if (__classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupComplete, "f")) {
5413
- const isTestnet = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode();
5414
- const network = isTestnet ? 'testnet' : 'mainnet';
5415
- const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
5416
- const cacheKey = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCacheKey).call(this, network, userAddress);
5417
- if (!__classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").has(cacheKey)) {
5418
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Retrying builder fee approval (previous attempt failed)');
5419
- try {
5420
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureBuilderFeeApproval).call(this);
6083
+ if (!__classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupComplete, "f") && !__classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f")) {
6084
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
6085
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Starting shared trading setup');
6086
+ __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, (async () => {
6087
+ // Pre-fetch spotMeta for HIP-3 operations (non-blocking if it fails)
6088
+ // This ensures token info (e.g. USDC token index) is available during order placement
6089
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_hip3Enabled, "f")) {
6090
+ try {
6091
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCachedSpotMeta).call(this);
6092
+ }
6093
+ catch (error) {
6094
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] spotMeta pre-fetch failed, will retry when needed', error);
6095
+ // Don't throw - spotMeta will be fetched on-demand if needed
6096
+ }
5421
6097
  }
5422
- catch (error) {
5423
- // Don't throw - retry is best-effort, trading continues regardless
5424
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Builder fee retry failed', error);
6098
+ // Set up referral code independently from builder-fee applicability.
6099
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReferralSet).call(this);
6100
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Trading setup completion');
6101
+ // Only mark complete if keyring was unlocked (signing could actually happen)
6102
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").isKeyringUnlocked()) {
6103
+ __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupComplete, true, "f");
5425
6104
  }
5426
- }
5427
- return;
5428
- }
5429
- // If trading setup is in progress, wait for it
5430
- if (__classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f")) {
5431
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Waiting for in-progress trading setup');
5432
- await __classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f");
5433
- return;
6105
+ })(), "f");
5434
6106
  }
5435
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Starting trading setup (may require signatures)');
5436
- __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, (async () => {
5437
- // Pre-fetch spotMeta for HIP-3 operations (non-blocking if it fails)
5438
- // This ensures token info (e.g. USDC token index) is available during order placement
5439
- if (__classPrivateFieldGet(this, _HyperLiquidProvider_hip3Enabled, "f")) {
5440
- try {
5441
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCachedSpotMeta).call(this);
5442
- }
5443
- catch (error) {
5444
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] spotMeta pre-fetch failed, will retry when needed', error);
5445
- // Don't throw - spotMeta will be fetched on-demand if needed
5446
- }
5447
- }
5448
- // Set up builder fee approval
6107
+ const pendingSetup = __classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f");
6108
+ if (pendingSetup) {
5449
6109
  try {
5450
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureBuilderFeeApproval).call(this);
5451
- }
5452
- catch (error) {
5453
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquidProvider: Builder fee approval failed', error);
5454
- // Don't throw - let trading continue, will fail with clear error if needed
6110
+ await pendingSetup;
5455
6111
  }
5456
- // Set up referral code
5457
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReferralSet).call(this);
5458
- // Only mark complete if keyring was unlocked (signing could actually happen)
5459
- if (__classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").isKeyringUnlocked()) {
5460
- __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupComplete, true, "f");
6112
+ finally {
6113
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f") === pendingSetup) {
6114
+ __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, null, "f");
6115
+ }
5461
6116
  }
5462
- })(), "f");
5463
- try {
5464
- await __classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f");
5465
- }
5466
- finally {
5467
- __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, null, "f");
5468
6117
  }
6118
+ const builderFeeSetupContext = options.requiresBuilderFee
6119
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureBuilderFeeSetup).call(this, options.builderFeeApprovalFailureCode)
6120
+ : undefined;
5469
6121
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureReadyForTrading] Trading setup complete');
6122
+ return builderFeeSetupContext;
5470
6123
  }, _HyperLiquidProvider_getOrFetchPrice =
5471
6124
  /**
5472
6125
  * Get current price for a symbol using WebSocket cache first, REST API fallback
@@ -5529,6 +6182,7 @@ async function _HyperLiquidProvider_getOrFetchPrice(params) {
5529
6182
  * @returns Array of all DEX names (null for main DEX, strings for HIP-3 DEXs)
5530
6183
  */
5531
6184
  async function _HyperLiquidProvider_getAllAvailableDexs() {
6185
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5532
6186
  // Use unified state if available
5533
6187
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").state) {
5534
6188
  const availableHip3Dexs = [];
@@ -5546,6 +6200,7 @@ async function _HyperLiquidProvider_getAllAvailableDexs() {
5546
6200
  if (!allDexs || !Array.isArray(allDexs)) {
5547
6201
  return [null]; // Fallback to main DEX only
5548
6202
  }
6203
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'DEX discovery cache write');
5549
6204
  const state = __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").update(allDexs);
5550
6205
  const availableHip3Dexs = [];
5551
6206
  state.raw.forEach((dex) => {
@@ -5592,14 +6247,17 @@ async function _HyperLiquidProvider_getValidatedDexs() {
5592
6247
  return __classPrivateFieldGet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, "f");
5593
6248
  }
5594
6249
  // Create and cache the pending promise for deduplication
5595
- __classPrivateFieldSet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchValidatedDexsInternal).call(this), "f");
6250
+ const pendingPromise = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchValidatedDexsInternal).call(this);
6251
+ __classPrivateFieldSet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, pendingPromise, "f");
5596
6252
  try {
5597
- const result = await __classPrivateFieldGet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, "f");
6253
+ const result = await pendingPromise;
5598
6254
  return result;
5599
6255
  }
5600
6256
  finally {
5601
- // Clear the pending promise when done (success or error)
5602
- __classPrivateFieldSet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, null, "f");
6257
+ // A stale request must not clear a newer lifecycle's pending request.
6258
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, "f") === pendingPromise) {
6259
+ __classPrivateFieldSet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, null, "f");
6260
+ }
5603
6261
  }
5604
6262
  }, _HyperLiquidProvider_fetchValidatedDexsInternal =
5605
6263
  /**
@@ -5609,6 +6267,7 @@ async function _HyperLiquidProvider_getValidatedDexs() {
5609
6267
  * @returns A promise that resolves to the result.
5610
6268
  */
5611
6269
  async function _HyperLiquidProvider_fetchValidatedDexsInternal() {
6270
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5612
6271
  // Kill switch: HIP-3 disabled, return main DEX only
5613
6272
  if (!__classPrivateFieldGet(this, _HyperLiquidProvider_hip3Enabled, "f")) {
5614
6273
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquidProvider: HIP-3 disabled via hip3Enabled flag');
@@ -5638,6 +6297,7 @@ async function _HyperLiquidProvider_fetchValidatedDexsInternal() {
5638
6297
  return [null];
5639
6298
  }
5640
6299
  // Atomically update unified state (raw + validated + timestamp)
6300
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'DEX discovery cache write');
5641
6301
  const state = __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").update(allDexs);
5642
6302
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquidProvider: Available DEXs (market filtering applied at data layer)', {
5643
6303
  count: state.validated.filter((dex) => dex !== null).length,
@@ -5656,13 +6316,12 @@ async function _HyperLiquidProvider_fetchValidatedDexsInternal() {
5656
6316
  * @throws Error if API returns invalid data
5657
6317
  */
5658
6318
  async function _HyperLiquidProvider_getCachedMeta(params) {
6319
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5659
6320
  const { dexName, skipCache } = params;
5660
- // Use empty string for main DEX key (consistent with buildAssetMapping cache population)
5661
- const dexKey = dexName ?? '';
5662
- const dexDisplayName = dexKey || 'main';
6321
+ const dexDisplayName = dexName ?? 'main';
5663
6322
  // Skip cache if requested (forces fresh fetch)
5664
6323
  if (!skipCache) {
5665
- const cached = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dexKey);
6324
+ const cached = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dexName);
5666
6325
  if (cached) {
5667
6326
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedMeta] Using cached meta response', {
5668
6327
  dex: dexDisplayName,
@@ -5672,6 +6331,29 @@ async function _HyperLiquidProvider_getCachedMeta(params) {
5672
6331
  }
5673
6332
  }
5674
6333
  // Cache miss or skipCache=true - fetch from API.
6334
+ const meta = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_fetchMeta).call(this, dexName);
6335
+ const cached = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheDexMetadataFromRead).call(this, {
6336
+ dex: dexName,
6337
+ meta,
6338
+ lifecycleGeneration,
6339
+ });
6340
+ if (cached) {
6341
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedMeta] Fetched and cached meta response', {
6342
+ dex: dexDisplayName,
6343
+ universeSize: meta.universe.length,
6344
+ skipCache,
6345
+ });
6346
+ }
6347
+ return meta;
6348
+ }, _HyperLiquidProvider_fetchMeta =
6349
+ /**
6350
+ * Fetch and validate metadata without mutating provider caches.
6351
+ *
6352
+ * @param dexName - DEX name, or null for the main DEX.
6353
+ * @returns Validated metadata for the requested DEX.
6354
+ */
6355
+ async function _HyperLiquidProvider_fetchMeta(dexName) {
6356
+ const dexDisplayName = dexName ?? 'main';
5675
6357
  // Bring the SDK clients up first. This is the first client touch on the
5676
6358
  // write path — placeOrder resolves asset info before it ensures trading
5677
6359
  // readiness — so without it a cold start or a post-disconnect action fails
@@ -5683,20 +6365,54 @@ async function _HyperLiquidProvider_getCachedMeta(params) {
5683
6365
  const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient({ useHttp: true });
5684
6366
  // Pass dex only for HIP-3 DEXs; omit for main DEX (empty string).
5685
6367
  // Testnet API returns null when dex="" is explicitly sent.
5686
- const meta = await infoClient.meta(dexKey ? { dex: dexKey } : undefined);
6368
+ const meta = await infoClient.meta(dexName ? { dex: dexName } : undefined);
5687
6369
  // Defensive validation before caching
5688
6370
  if (!meta?.universe || !Array.isArray(meta.universe)) {
5689
6371
  throw new Error(`[HyperLiquidProvider] Invalid meta response for DEX ${dexDisplayName}: universe is ${meta?.universe ? 'not an array' : 'missing'}`);
5690
6372
  }
5691
- // Store raw meta response for reuse
5692
- __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").set(dexKey, meta);
5693
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dexName, meta);
5694
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedMeta] Fetched and cached meta response', {
5695
- dex: dexDisplayName,
5696
- universeSize: meta.universe.length,
5697
- skipCache,
5698
- });
5699
6373
  return meta;
6374
+ }, _HyperLiquidProvider_cacheDexMetadata = function _HyperLiquidProvider_cacheDexMetadata(params) {
6375
+ const { dex, meta, assetCtxs, lifecycleGeneration } = params;
6376
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'DEX metadata cache write');
6377
+ __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").set(dex, meta);
6378
+ if (assetCtxs) {
6379
+ const subscriptionDexKey = dex ?? '';
6380
+ __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexMetaCache(subscriptionDexKey, meta);
6381
+ __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexAssetCtxsCache(subscriptionDexKey, assetCtxs);
6382
+ }
6383
+ }, _HyperLiquidProvider_cacheDexMetadataFromRead =
6384
+ /**
6385
+ * Cache metadata fetched for a read without discarding a valid response.
6386
+ *
6387
+ * @param params - Metadata and lifecycle context.
6388
+ * @param params.dex - DEX name, or null for the main DEX.
6389
+ * @param params.meta - Validated DEX metadata.
6390
+ * @param params.assetCtxs - Asset contexts shared with subscriptions, if any.
6391
+ * @param params.lifecycleGeneration - Lifecycle that produced the metadata.
6392
+ * @returns Whether the fetched metadata was cached.
6393
+ */
6394
+ async function _HyperLiquidProvider_cacheDexMetadataFromRead(params) {
6395
+ const { dex, meta, assetCtxs, lifecycleGeneration } = params;
6396
+ if (!__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'DEX metadata read cache write')) {
6397
+ return false;
6398
+ }
6399
+ try {
6400
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheDexMetadata).call(this, {
6401
+ dex,
6402
+ meta,
6403
+ assetCtxs,
6404
+ lifecycleGeneration,
6405
+ });
6406
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dex, meta, lifecycleGeneration);
6407
+ return true;
6408
+ }
6409
+ catch (error) {
6410
+ if ((0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.cacheDexMetadataFromRead')
6411
+ .message === perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE) {
6412
+ return false;
6413
+ }
6414
+ throw error;
6415
+ }
5700
6416
  }, _HyperLiquidProvider_backfillAssetMapForDex =
5701
6417
  /**
5702
6418
  * Backfill the asset ID map for a single DEX from a fresh meta response.
@@ -5704,9 +6420,11 @@ async function _HyperLiquidProvider_getCachedMeta(params) {
5704
6420
  *
5705
6421
  * @param dex - DEX name (null for main DEX).
5706
6422
  * @param meta - Meta response containing the DEX universe.
6423
+ * @param lifecycleGeneration - Lifecycle that produced the metadata.
5707
6424
  * @returns True if the mapping was rebuilt for the DEX.
5708
6425
  */
5709
- async function _HyperLiquidProvider_backfillAssetMapForDex(dex, meta) {
6426
+ async function _HyperLiquidProvider_backfillAssetMapForDex(dex, meta, lifecycleGeneration) {
6427
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Asset map backfill');
5710
6428
  if (!meta?.universe || !Array.isArray(meta.universe)) {
5711
6429
  return false;
5712
6430
  }
@@ -5721,6 +6439,7 @@ async function _HyperLiquidProvider_backfillAssetMapForDex(dex, meta) {
5721
6439
  });
5722
6440
  }
5723
6441
  }
6442
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Asset map backfill');
5724
6443
  const allPerpDexs = __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").state?.raw ?? [null];
5725
6444
  const perpDexIndex = allPerpDexs.findIndex((entry) => {
5726
6445
  if (dex === null) {
@@ -5757,6 +6476,7 @@ async function _HyperLiquidProvider_backfillAssetMapForDex(dex, meta) {
5757
6476
  * @returns The asset ID.
5758
6477
  */
5759
6478
  async function _HyperLiquidProvider_getAssetIdWithRepair(params) {
6479
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5760
6480
  const { symbol, dexName } = params;
5761
6481
  const existingAssetId = __classPrivateFieldGet(this, _HyperLiquidProvider_symbolToAssetId, "f").get(symbol);
5762
6482
  if (existingAssetId !== undefined) {
@@ -5765,7 +6485,7 @@ async function _HyperLiquidProvider_getAssetIdWithRepair(params) {
5765
6485
  const meta = params.meta ?? (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCachedMeta).call(this, { dexName: dexName ?? null }));
5766
6486
  const assetExistsInMeta = meta.universe.some((asset) => asset.name === symbol);
5767
6487
  if (assetExistsInMeta) {
5768
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dexName, meta);
6488
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dexName, meta, lifecycleGeneration);
5769
6489
  const repairedAssetId = __classPrivateFieldGet(this, _HyperLiquidProvider_symbolToAssetId, "f").get(symbol);
5770
6490
  if (repairedAssetId !== undefined) {
5771
6491
  return repairedAssetId;
@@ -5796,13 +6516,16 @@ async function _HyperLiquidProvider_getCachedSpotMeta() {
5796
6516
  });
5797
6517
  return __classPrivateFieldGet(this, _HyperLiquidProvider_cachedSpotMeta, "f");
5798
6518
  }
6519
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5799
6520
  const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient();
5800
6521
  const spotMeta = await infoClient.spotMeta();
5801
- __classPrivateFieldSet(this, _HyperLiquidProvider_cachedSpotMeta, spotMeta, "f");
5802
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedSpotMeta] Fetched and cached spotMeta', {
5803
- tokensCount: spotMeta.tokens.length,
5804
- universeCount: spotMeta.universe.length,
5805
- });
6522
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Spot metadata cache write')) {
6523
+ __classPrivateFieldSet(this, _HyperLiquidProvider_cachedSpotMeta, spotMeta, "f");
6524
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedSpotMeta] Fetched and cached spotMeta', {
6525
+ tokensCount: spotMeta.tokens.length,
6526
+ universeCount: spotMeta.universe.length,
6527
+ });
6528
+ }
5806
6529
  return spotMeta;
5807
6530
  }, _HyperLiquidProvider_getCachedPerpDexs =
5808
6531
  /**
@@ -5812,6 +6535,7 @@ async function _HyperLiquidProvider_getCachedSpotMeta() {
5812
6535
  * @returns Array of ExtendedPerpDex objects (null entries represent main DEX)
5813
6536
  */
5814
6537
  async function _HyperLiquidProvider_getCachedPerpDexs() {
6538
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
5815
6539
  const now = Date.now();
5816
6540
  // Return cached data if still valid (uses unified state timestamp for TTL)
5817
6541
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").state &&
@@ -5829,17 +6553,19 @@ async function _HyperLiquidProvider_getCachedPerpDexs() {
5829
6553
  await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureClientsInitialized).call(this);
5830
6554
  const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient();
5831
6555
  const perpDexs = (await infoClient.perpDexs());
5832
- // Atomically update unified state (raw + validated + timestamp)
5833
- __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").update(perpDexs);
5834
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedPerpDexs] Fetched and cached perpDexs data', {
5835
- count: perpDexs.length,
5836
- dexes: perpDexs
5837
- .filter((dex) => dex !== null)
5838
- .map((dex) => ({
5839
- name: dex.name,
5840
- deployerFeeScale: dex.deployerFeeScale,
5841
- })),
5842
- });
6556
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_isCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Perp DEX cache write')) {
6557
+ // Atomically update unified state (raw + validated + timestamp)
6558
+ __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").update(perpDexs);
6559
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getCachedPerpDexs] Fetched and cached perpDexs data', {
6560
+ count: perpDexs.length,
6561
+ dexes: perpDexs
6562
+ .filter((dex) => dex !== null)
6563
+ .map((dex) => ({
6564
+ name: dex.name,
6565
+ deployerFeeScale: dex.deployerFeeScale,
6566
+ })),
6567
+ });
6568
+ }
5843
6569
  return perpDexs;
5844
6570
  }, _HyperLiquidProvider_calculateHip3FeeMultiplier =
5845
6571
  /**
@@ -6011,6 +6737,7 @@ async function _HyperLiquidProvider_isUsdcCollateralDex(dexName) {
6011
6737
  * the asset ID lookup succeeds and the order routes to the correct DEX.
6012
6738
  */
6013
6739
  async function _HyperLiquidProvider_buildAssetMapping() {
6740
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
6014
6741
  // Get feature-flag-validated DEXs to map (respects hip3Enabled and enabledDexs)
6015
6742
  let dexsToMap;
6016
6743
  try {
@@ -6041,14 +6768,14 @@ async function _HyperLiquidProvider_buildAssetMapping() {
6041
6768
  // Extract HIP-3 DEX names (filter out null which represents main DEX)
6042
6769
  const enabledDexs = dexsToMap.filter((dex) => dex !== null);
6043
6770
  await __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").updateFeatureFlags(__classPrivateFieldGet(this, _HyperLiquidProvider_hip3Enabled, "f"), enabledDexs, __classPrivateFieldGet(this, _HyperLiquidProvider_allowlistMarkets, "f"), __classPrivateFieldGet(this, _HyperLiquidProvider_blocklistMarkets, "f"));
6771
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Asset mapping rebuild');
6044
6772
  // Fetch metadata for each DEX in parallel using metaAndAssetCtxs
6045
6773
  // Optimization: Check cache first - getMarketDataWithPrices may have already fetched
6046
6774
  // If not cached, fetch via metaAndAssetCtxs and populate cache for other methods
6047
6775
  const infoClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient();
6048
6776
  const allMetas = await Promise.allSettled(dexsToMap.map((dex) => {
6049
- const dexKey = dex ?? '';
6050
6777
  // Check if already cached (e.g., by getMarketDataWithPrices running in parallel)
6051
- const cachedMeta = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dexKey);
6778
+ const cachedMeta = __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").get(dex);
6052
6779
  if (cachedMeta) {
6053
6780
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log(`[buildAssetMapping] Using cached meta for ${dex ?? 'main'}`, { universeSize: cachedMeta.universe.length });
6054
6781
  return Promise.resolve({
@@ -6066,11 +6793,12 @@ async function _HyperLiquidProvider_buildAssetMapping() {
6066
6793
  const assetCtxs = result?.[1] || [];
6067
6794
  // Cache meta for later use by getCachedMeta
6068
6795
  if (meta?.universe) {
6069
- __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").set(dexKey, meta);
6070
- // Also populate subscription service cache to avoid redundant API calls
6071
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexMetaCache(dexKey, meta);
6072
- // Cache assetCtxs for getMarketDataWithPrices (avoids duplicate metaAndAssetCtxs calls)
6073
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexAssetCtxsCache(dexKey, assetCtxs);
6796
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheDexMetadata).call(this, {
6797
+ dex,
6798
+ meta,
6799
+ assetCtxs,
6800
+ lifecycleGeneration,
6801
+ });
6074
6802
  }
6075
6803
  return { dex, meta, success: true };
6076
6804
  })
@@ -6079,6 +6807,7 @@ async function _HyperLiquidProvider_buildAssetMapping() {
6079
6807
  return { dex, meta: null, success: false };
6080
6808
  });
6081
6809
  }));
6810
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertCacheWriteLifecycleCurrent).call(this, lifecycleGeneration, 'Asset mapping rebuild');
6082
6811
  // Build mapping with DEX prefixes for HIP-3 DEXs using the utility function
6083
6812
  __classPrivateFieldGet(this, _HyperLiquidProvider_symbolToAssetId, "f").clear();
6084
6813
  let dexDiscoveryComplete = __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").state !== null;
@@ -6250,12 +6979,16 @@ async function _HyperLiquidProvider_checkBuilderFeeApproval(builder, userAddress
6250
6979
  * This prevents repeated signing requests for hardware wallets.
6251
6980
  *
6252
6981
  * Note: This is network-specific - testnet and mainnet have separate builder fee states
6982
+ *
6983
+ * @param params - Builder fee setup context.
6984
+ * @param params.network - HyperLiquid network for the approval.
6985
+ * @param params.userAddress - Account that owns the approval.
6986
+ * @param params.builderAddress - Builder address being approved.
6253
6987
  */
6254
- async function _HyperLiquidProvider_ensureBuilderFeeApproval() {
6255
- const isTestnet = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode();
6256
- const network = isTestnet ? 'testnet' : 'mainnet';
6257
- const builderAddress = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderAddress).call(this, isTestnet);
6258
- const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
6988
+ async function _HyperLiquidProvider_ensureBuilderFeeApproval(params) {
6989
+ const lifecycleGeneration = __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f");
6990
+ const { network, userAddress, builderAddress } = params;
6991
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Builder fee approval');
6259
6992
  const cacheKey = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getCacheKey).call(this, network, userAddress);
6260
6993
  // Check GLOBAL cache first to avoid repeated signing requests across reconnections
6261
6994
  // This is CRITICAL for hardware wallets to prevent repeated signing prompts
@@ -6272,6 +7005,11 @@ async function _HyperLiquidProvider_ensureBuilderFeeApproval() {
6272
7005
  if (inFlightPromise) {
6273
7006
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureBuilderFeeApproval] Global in-flight, waiting...', { network });
6274
7007
  await inFlightPromise;
7008
+ const completedApproval = TradingReadinessCache_js_1.PerpsSigningCache.getBuilderFee(network, userAddress);
7009
+ if (completedApproval?.attempted && completedApproval.success) {
7010
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").set(cacheKey, true);
7011
+ __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").add(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getApprovedBuilderKey).call(this, network, userAddress, builderAddress));
7012
+ }
6275
7013
  return;
6276
7014
  }
6277
7015
  // Set global in-flight lock
@@ -6281,10 +7019,13 @@ async function _HyperLiquidProvider_ensureBuilderFeeApproval() {
6281
7019
  const recheckCache = TradingReadinessCache_js_1.PerpsSigningCache.getBuilderFee(network, userAddress);
6282
7020
  if (recheckCache?.attempted && recheckCache?.success) {
6283
7021
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureBuilderFeeApproval] Completed by another provider', { network });
7022
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").set(cacheKey, true);
7023
+ __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").add(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getApprovedBuilderKey).call(this, network, userAddress, builderAddress));
6284
7024
  completeInFlight();
6285
7025
  return;
6286
7026
  }
6287
7027
  const { isApproved, requiredDecimal } = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_checkBuilderFeeStatus).call(this, builderAddress, userAddress);
7028
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Builder fee approval');
6288
7029
  if (isApproved) {
6289
7030
  // User already has approval on-chain
6290
7031
  TradingReadinessCache_js_1.PerpsSigningCache.setBuilderFee(network, userAddress, {
@@ -6309,11 +7050,15 @@ async function _HyperLiquidProvider_ensureBuilderFeeApproval() {
6309
7050
  afterApprovalDecimal < requiredDecimal) {
6310
7051
  throw new Error('[HyperLiquidProvider] Builder fee approval verification failed');
6311
7052
  }
6312
- // Cache success in BOTH global and instance caches
7053
+ // The approval is already confirmed on-chain. Preserve that fact for
7054
+ // the next provider lifecycle even if this instance became stale while
7055
+ // the approval request was in flight.
6313
7056
  TradingReadinessCache_js_1.PerpsSigningCache.setBuilderFee(network, userAddress, {
6314
7057
  attempted: true,
6315
7058
  success: true,
6316
7059
  });
7060
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_assertProviderLifecycleCurrent).call(this, lifecycleGeneration, 'Builder fee approval');
7061
+ // Cache success in the current instance after the lifecycle check.
6317
7062
  __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").set(cacheKey, true);
6318
7063
  __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").add(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getApprovedBuilderKey).call(this, network, userAddress, builderAddress));
6319
7064
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[ensureBuilderFeeApproval] Approval successful', {
@@ -6324,6 +7069,11 @@ async function _HyperLiquidProvider_ensureBuilderFeeApproval() {
6324
7069
  completeInFlight();
6325
7070
  }
6326
7071
  catch (error) {
7072
+ if ((0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.ensureBuilderFeeApproval')
7073
+ .message === perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE) {
7074
+ completeInFlight();
7075
+ return;
7076
+ }
6327
7077
  // HyperLiquid wraps wallet signing failures and preserves KEYRING_LOCKED
6328
7078
  // in `cause`, so classify the full chain and leave retry caches empty.
6329
7079
  if ((0, errorUtils_js_1.isKeyringLockedError)(error)) {
@@ -6661,6 +7411,7 @@ async function _HyperLiquidProvider_calculateHip3RequiredMargin(params) {
6661
7411
  * @param params.transferInfo - The transfer information.
6662
7412
  * @param params.transferInfo.amount - The amount value.
6663
7413
  * @param params.transferInfo.sourceDex - The source DEX for the transfer.
7414
+ * @returns Whether the balance check and any required transfer succeeded.
6664
7415
  * @private
6665
7416
  */
6666
7417
  async function _HyperLiquidProvider_handleHip3PostOrderRebalance(params) {
@@ -6689,16 +7440,20 @@ async function _HyperLiquidProvider_handleHip3PostOrderRebalance(params) {
6689
7440
  excessAmount: excessAmount.toFixed(2),
6690
7441
  destinationDex: transferInfo.sourceDex,
6691
7442
  });
6692
- await this.transferBetweenDexs({
7443
+ const transferResult = await this.transferBetweenDexs({
6693
7444
  sourceDex: dexName,
6694
7445
  destinationDex: transferInfo.sourceDex,
6695
7446
  amount: excessAmount.toFixed(hyperLiquidConfig_js_1.USDC_DECIMALS),
6696
7447
  });
7448
+ if (!transferResult.success) {
7449
+ throw new Error(transferResult.error ?? perpsErrorCodes_js_1.PERPS_ERROR_CODES.TRANSFER_FAILED);
7450
+ }
6697
7451
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('✅ HyperLiquidProvider: Auto-rebalance completed', {
6698
7452
  transferredBack: excessAmount.toFixed(2),
6699
7453
  from: dexName,
6700
7454
  to: transferInfo.sourceDex,
6701
7455
  });
7456
+ return true;
6702
7457
  }
6703
7458
  catch (rebalanceError) {
6704
7459
  // Don't fail the order if rebalance fails (order already succeeded)
@@ -6707,6 +7462,7 @@ async function _HyperLiquidProvider_handleHip3PostOrderRebalance(params) {
6707
7462
  excessAmount: excessAmount.toFixed(2),
6708
7463
  note: 'Auto-rebalance failed - funds remain on HIP-3 DEX',
6709
7464
  }));
7465
+ return false;
6710
7466
  }
6711
7467
  }
6712
7468
  else {
@@ -6715,6 +7471,7 @@ async function _HyperLiquidProvider_handleHip3PostOrderRebalance(params) {
6715
7471
  threshold: minimumTransferThreshold.toFixed(2),
6716
7472
  note: 'Excess below minimum transfer threshold',
6717
7473
  });
7474
+ return true;
6718
7475
  }
6719
7476
  }
6720
7477
  catch (balanceCheckError) {
@@ -6723,6 +7480,7 @@ async function _HyperLiquidProvider_handleHip3PostOrderRebalance(params) {
6723
7480
  dex: dexName,
6724
7481
  note: 'Failed to verify post-order balance for auto-rebalance',
6725
7482
  }));
7483
+ return false;
6726
7484
  }
6727
7485
  }, _HyperLiquidProvider_handleHip3OrderRollback =
6728
7486
  /**
@@ -6906,7 +7664,10 @@ async function _HyperLiquidProvider_handleHip3PreOrder(params) {
6906
7664
  async function _HyperLiquidProvider_submitOrderWithRollback(params) {
6907
7665
  const { orders, grouping, isHip3Order, dexName, transferInfo, symbol } = params;
6908
7666
  const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
6909
- const builder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this);
7667
+ const builder = params.chargesMetamaskBuilderFee
7668
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this, params.builderFeeSetupContext ??
7669
+ (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureBuilderFeeSetup).call(this)))
7670
+ : undefined;
6910
7671
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Submitting order via asset ID routing', {
6911
7672
  symbol,
6912
7673
  assetId: orders[0].a,
@@ -6919,7 +7680,7 @@ async function _HyperLiquidProvider_submitOrderWithRollback(params) {
6919
7680
  const result = await exchangeClient.order({
6920
7681
  orders,
6921
7682
  grouping,
6922
- builder,
7683
+ ...(builder && { builder }),
6923
7684
  });
6924
7685
  if (result.status !== 'ok') {
6925
7686
  throw new Error(`Order failed: ${JSON.stringify(result)}`);
@@ -6994,18 +7755,22 @@ async function _HyperLiquidProvider_handleOrderError(params) {
6994
7755
  async function _HyperLiquidProvider_placeStrategyOrder(params, orderType) {
6995
7756
  // Captured before the shared preamble, not after it: that preamble awaits
6996
7757
  // asset info, validation, trading readiness and a leverage update, and a
6997
- // disconnect during any of them has to be seen by the chase registration
6998
- // at the end.
7758
+ // disconnect during any of them has to be seen before or after submission.
7759
+ const generation = __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f");
6999
7760
  if (orderType !== 'chase') {
7000
7761
  const context = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_prepareStrategyPlacement).call(this, params);
7001
- return orderType === 'twap'
7002
- ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_placeTwapOrder).call(this, params, context)
7003
- : await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_placeScaleOrder).call(this, params, context);
7762
+ return await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_submitPreparedStrategy).call(this, params, context, async () => {
7763
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7764
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
7765
+ }
7766
+ return orderType === 'twap'
7767
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_placeTwapOrder).call(this, params, context, generation)
7768
+ : await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_placeScaleOrder).call(this, params, context, generation);
7769
+ });
7004
7770
  }
7005
7771
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") > 0) {
7006
7772
  throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7007
7773
  }
7008
- const generation = __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f");
7009
7774
  // The chase slot is claimed before the preamble, not after it: the preamble
7010
7775
  // completes the signing setup and can change the asset's leverage, and a
7011
7776
  // request that is going to be refused for exceeding the venue's cap must
@@ -7027,7 +7792,7 @@ async function _HyperLiquidProvider_placeStrategyOrder(params, orderType) {
7027
7792
  try {
7028
7793
  await __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f");
7029
7794
  const context = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_prepareStrategyPlacement).call(this, params);
7030
- return await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_startChaseSession).call(this, params, context, generation);
7795
+ 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));
7031
7796
  }
7032
7797
  finally {
7033
7798
  settlePlacement?.();
@@ -7037,6 +7802,74 @@ async function _HyperLiquidProvider_placeStrategyOrder(params, orderType) {
7037
7802
  __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resumeChaseTicksAfterPlacement).call(this);
7038
7803
  }
7039
7804
  }
7805
+ }, _HyperLiquidProvider_submitPreparedStrategy =
7806
+ /**
7807
+ * Submit a prepared strategy and settle any manual HIP-3 collateral move.
7808
+ *
7809
+ * Scale and Chase leave regular orders or fills on the venue, so the normal
7810
+ * HIP-3 post-order balance check can return any unreserved excess. Native
7811
+ * TWAP executes future slices, so its transferred collateral stays on the
7812
+ * routed DEX until cancellation confirms the schedule has stopped.
7813
+ *
7814
+ * @param params - Strategy order parameters.
7815
+ * @param context - Prepared placement and HIP-3 transfer context.
7816
+ * @param submit - Venue submission.
7817
+ * @returns The strategy placement result.
7818
+ */
7819
+ async function _HyperLiquidProvider_submitPreparedStrategy(params, context, submit) {
7820
+ const { dexName, network, transferInfo, userAddress } = context;
7821
+ let result;
7822
+ try {
7823
+ result = await submit();
7824
+ }
7825
+ catch (error) {
7826
+ if (dexName && transferInfo) {
7827
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3OrderRollback).call(this, { dexName, transferInfo });
7828
+ }
7829
+ throw error;
7830
+ }
7831
+ const hasVenueExposure = result.success === true ||
7832
+ result.orderId !== undefined ||
7833
+ (result.childOrderIds?.length ?? 0) > 0;
7834
+ if (dexName && transferInfo && !hasVenueExposure) {
7835
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3OrderRollback).call(this, { dexName, transferInfo });
7836
+ return result;
7837
+ }
7838
+ if (params.orderType === 'twap' && result.orderId) {
7839
+ __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").set(getTwapOrderScopeKey({
7840
+ network,
7841
+ userAddress,
7842
+ orderId: result.orderId,
7843
+ }), {
7844
+ symbol: params.symbol,
7845
+ ...(dexName && transferInfo
7846
+ ? { hip3Transfer: { dexName, transferInfo } }
7847
+ : {}),
7848
+ });
7849
+ return result;
7850
+ }
7851
+ if (params.orderType === 'chase' &&
7852
+ result.success &&
7853
+ result.orderId &&
7854
+ dexName &&
7855
+ transferInfo) {
7856
+ const session = __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").get(result.orderId);
7857
+ if (session) {
7858
+ session.hip3Transfer = { dexName, transferInfo };
7859
+ return result;
7860
+ }
7861
+ }
7862
+ if (params.orderType === 'chase' && hasVenueExposure) {
7863
+ // A stale placement can fail after its child rests and the best-effort
7864
+ // retraction is refused. With no session left to own that child, keep
7865
+ // manual HIP-3 collateral on its DEX until the caller cancels the
7866
+ // reported exchange ID.
7867
+ return result;
7868
+ }
7869
+ if (dexName && transferInfo) {
7870
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3PostOrderRebalance).call(this, { dexName, transferInfo });
7871
+ }
7872
+ return result;
7040
7873
  }, _HyperLiquidProvider_prepareStrategyPlacement =
7041
7874
  /**
7042
7875
  * Run the shared preamble every strategy placement needs.
@@ -7050,13 +7883,6 @@ async function _HyperLiquidProvider_placeStrategyOrder(params, orderType) {
7050
7883
  */
7051
7884
  async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7052
7885
  const { dex: dexName } = (0, hyperLiquidAdapter_js_1.parseAssetName)(params.symbol);
7053
- // A HIP-3 order is preceded by a margin transfer onto the builder DEX and
7054
- // followed by a rebalance, both of which are wired into the single-order
7055
- // submit path and its rollback. Rather than half-support that here, a
7056
- // strategy placement on a HIP-3 market is refused outright.
7057
- if (dexName !== null) {
7058
- throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_STRATEGY_MARKET_UNSUPPORTED);
7059
- }
7060
7886
  const { assetInfo, currentPrice, meta } = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetInfo).call(this, {
7061
7887
  symbol: params.symbol,
7062
7888
  dexName,
@@ -7117,7 +7943,10 @@ async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7117
7943
  }
7118
7944
  // Kept after validation so an invalid strategy order never triggers the
7119
7945
  // signature prompts in trading setup — same ordering as `placeOrder`.
7120
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
7946
+ const { chargesMetamaskBuilderFee } = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_resolveOrderFeePolicy).call(this, params);
7947
+ const builderFeeSetupContext = chargesMetamaskBuilderFee
7948
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: true })
7949
+ : await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
7121
7950
  const assetId = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
7122
7951
  symbol: params.symbol,
7123
7952
  dexName,
@@ -7128,12 +7957,38 @@ async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7128
7957
  assetId,
7129
7958
  leverage: params.leverage,
7130
7959
  });
7960
+ const builder = builderFeeSetupContext
7961
+ ? await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this, builderFeeSetupContext)
7962
+ : undefined;
7963
+ const network = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode() ? 'testnet' : 'mainnet';
7964
+ const userAddress = builderFeeSetupContext?.userAddress ??
7965
+ (await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault());
7966
+ let transferInfo = null;
7967
+ if (dexName) {
7968
+ const orderPrice = ladder === undefined
7969
+ ? effectivePrice
7970
+ : Math.max(...ladder.prices.map(Number.parseFloat));
7971
+ const hip3Result = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3PreOrder).call(this, {
7972
+ dexName,
7973
+ symbol: params.symbol,
7974
+ orderPrice,
7975
+ positionSize: parseFloat(formattedSize),
7976
+ leverage: params.leverage ?? assetInfo.maxLeverage ?? 1,
7977
+ isBuy: params.isBuy,
7978
+ maxLeverage: assetInfo.maxLeverage,
7979
+ });
7980
+ transferInfo = hip3Result.transferInfo;
7981
+ }
7131
7982
  return {
7132
7983
  assetId,
7133
7984
  szDecimals: assetInfo.szDecimals,
7134
- finalPositionSize,
7135
7985
  formattedSize,
7136
7986
  ladder,
7987
+ builder,
7988
+ dexName,
7989
+ network,
7990
+ transferInfo,
7991
+ userAddress,
7137
7992
  };
7138
7993
  }, _HyperLiquidProvider_getMinimumOrderSize = function _HyperLiquidProvider_getMinimumOrderSize() {
7139
7994
  return __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode()
@@ -7141,11 +7996,17 @@ async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7141
7996
  : hyperLiquidConfig_js_1.TRADING_DEFAULTS.amount.mainnet;
7142
7997
  }, _HyperLiquidProvider_buildScaleLadder = function _HyperLiquidProvider_buildScaleLadder(options) {
7143
7998
  const { params, szDecimals, finalPositionSize } = options;
7144
- const count = params.scaleNumOrders;
7999
+ const { scaleMinPrice, scaleMaxPrice, scaleNumOrders } = params;
8000
+ if (scaleMinPrice === undefined || scaleMaxPrice === undefined) {
8001
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_SCALE_RANGE_REQUIRED);
8002
+ }
8003
+ if (scaleNumOrders === undefined) {
8004
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_SCALE_COUNT_INVALID);
8005
+ }
7145
8006
  const prices = (0, orderCalculations_js_1.computeScalePriceLadder)({
7146
- minPrice: parseFloat(params.scaleMinPrice),
7147
- maxPrice: parseFloat(params.scaleMaxPrice),
7148
- count,
8007
+ minPrice: parseFloat(scaleMinPrice),
8008
+ maxPrice: parseFloat(scaleMaxPrice),
8009
+ count: scaleNumOrders,
7149
8010
  }).map((price) => (0, hyperLiquidAdapter_js_1.formatHyperLiquidPrice)({ price, szDecimals }));
7150
8011
  if (new Set(prices).size !== prices.length) {
7151
8012
  throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_SCALE_RANGE_INVALID);
@@ -7154,7 +8015,7 @@ async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7154
8015
  // which a skew weighted far enough from even can do on its own.
7155
8016
  const sizes = (0, orderCalculations_js_1.splitScaleSizes)({
7156
8017
  totalSize: finalPositionSize,
7157
- count,
8018
+ count: scaleNumOrders,
7158
8019
  szDecimals,
7159
8020
  skew: params.scaleSkew,
7160
8021
  });
@@ -7174,16 +8035,26 @@ async function _HyperLiquidProvider_prepareStrategyPlacement(params) {
7174
8035
  *
7175
8036
  * @param params - Order parameters.
7176
8037
  * @param context - Prepared asset and sizing context.
8038
+ * @param generation - Teardown generation captured before preparation.
7177
8039
  * @returns A promise that resolves to the result.
7178
8040
  */
7179
- async function _HyperLiquidProvider_placeTwapOrder(params, context) {
8041
+ async function _HyperLiquidProvider_placeTwapOrder(params, context, generation) {
7180
8042
  const { assetId, formattedSize } = context;
8043
+ const durationMinutes = params.twapDuration;
8044
+ if (durationMinutes === undefined) {
8045
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_TWAP_DURATION_REQUIRED);
8046
+ }
8047
+ if (!Number.isSafeInteger(durationMinutes) ||
8048
+ durationMinutes < perpsConfig_js_1.HYPERLIQUID_TWAP_LIMITS.MinDurationMinutes ||
8049
+ durationMinutes > perpsConfig_js_1.HYPERLIQUID_TWAP_LIMITS.MaxDurationMinutes) {
8050
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_TWAP_DURATION_INVALID);
8051
+ }
7181
8052
  const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
7182
8053
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Submitting TWAP order', {
7183
8054
  symbol: params.symbol,
7184
8055
  assetId,
7185
8056
  size: formattedSize,
7186
- durationMinutes: params.twapDuration,
8057
+ durationMinutes,
7187
8058
  randomize: params.twapRandomize ?? false,
7188
8059
  });
7189
8060
  const result = await exchangeClient.twapOrder({
@@ -7192,9 +8063,7 @@ async function _HyperLiquidProvider_placeTwapOrder(params, context) {
7192
8063
  b: params.isBuy,
7193
8064
  s: formattedSize,
7194
8065
  r: params.reduceOnly ?? false,
7195
- // Validation has already established this is a whole number of minutes
7196
- // inside the venue's bounds.
7197
- m: params.twapDuration,
8066
+ m: durationMinutes,
7198
8067
  t: params.twapRandomize ?? false,
7199
8068
  },
7200
8069
  });
@@ -7202,15 +8071,44 @@ async function _HyperLiquidProvider_placeTwapOrder(params, context) {
7202
8071
  throw new Error(`TWAP order failed: ${JSON.stringify(result)}`);
7203
8072
  }
7204
8073
  const status = result.response?.data?.status;
7205
- if (!isStatusObject(status) || !(0, utils_1.hasProperty)(status, 'running')) {
7206
- const rawError = status?.error ??
7207
- 'TWAP order rejected';
8074
+ if (!isStatusObject(status)) {
8075
+ throw new Error('TWAP order rejected');
8076
+ }
8077
+ const rawError = typeof status.error === 'string' ? status.error : 'TWAP order rejected';
8078
+ const { running } = status;
8079
+ if (!isStatusObject(running) ||
8080
+ typeof running.twapId !== 'number' ||
8081
+ !Number.isSafeInteger(running.twapId) ||
8082
+ running.twapId < 0) {
7208
8083
  throw new Error(rawError);
7209
8084
  }
7210
- const running = status.running;
8085
+ const orderId = running.twapId.toString();
8086
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
8087
+ let remainsLive = true;
8088
+ try {
8089
+ const cancelResult = await exchangeClient.twapCancel({
8090
+ a: assetId,
8091
+ t: running.twapId,
8092
+ });
8093
+ remainsLive =
8094
+ classifyCancelStatus(cancelResult.response?.data?.status) ===
8095
+ CancelChildOutcome.Refused;
8096
+ }
8097
+ catch (error) {
8098
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Stale TWAP placement could not be retracted', {
8099
+ error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.placeTwapOrder.retract').message,
8100
+ orderId,
8101
+ });
8102
+ }
8103
+ return (0, hyperLiquidValidation_js_1.createErrorResult)(new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE), {
8104
+ success: false,
8105
+ submittedSize: formattedSize,
8106
+ ...(remainsLive && { orderId }),
8107
+ });
8108
+ }
7211
8109
  return {
7212
8110
  success: true,
7213
- orderId: running.twapId.toString(),
8111
+ orderId,
7214
8112
  submittedSize: formattedSize,
7215
8113
  };
7216
8114
  }, _HyperLiquidProvider_placeScaleOrder =
@@ -7219,22 +8117,25 @@ async function _HyperLiquidProvider_placeTwapOrder(params, context) {
7219
8117
  *
7220
8118
  * The whole ladder goes in a single `order` action, which is one round trip
7221
8119
  * and one signature rather than one per rung. It is **not** atomic: an `na`
7222
- * grouping evaluates each entry independently, so the response can report
7223
- * some rungs resting and others rejected. A partial ladder is reported as
7224
- * such — `submittedSize` covers only the rungs that actually rested — rather
7225
- * than as a full submission.
8120
+ * grouping evaluates each entry independently. Every rung must either rest
8121
+ * or fill; otherwise all known resting rungs are retracted before failure.
7226
8122
  *
7227
8123
  * @param params - Order parameters.
7228
8124
  * @param context - Prepared asset and sizing context.
8125
+ * @param generation - Teardown generation captured before preparation.
7229
8126
  * @returns A promise that resolves to the result.
7230
8127
  */
7231
- async function _HyperLiquidProvider_placeScaleOrder(params, context) {
7232
- const { assetId, szDecimals, formattedSize, ladder } = context;
8128
+ async function _HyperLiquidProvider_placeScaleOrder(params, context, generation) {
8129
+ const { assetId, formattedSize, ladder, builder } = context;
7233
8130
  // Built and validated in `#prepareStrategyPlacement`, before anything was
7234
8131
  // signed, so what is submitted here is exactly what the minimums were
7235
8132
  // applied to.
8133
+ if (!ladder) {
8134
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_SCALE_RANGE_REQUIRED);
8135
+ }
7236
8136
  const { prices, sizes } = ladder;
7237
8137
  const count = prices.length;
8138
+ const { groupId, clientOrderIds } = createScaleOrderIdentity(count);
7238
8139
  const orders = prices.map((price, index) => ({
7239
8140
  a: assetId,
7240
8141
  b: params.isBuy,
@@ -7242,6 +8143,7 @@ async function _HyperLiquidProvider_placeScaleOrder(params, context) {
7242
8143
  s: sizes[index],
7243
8144
  r: params.reduceOnly ?? false,
7244
8145
  t: { limit: { tif: 'Gtc' } },
8146
+ c: clientOrderIds[index],
7245
8147
  }));
7246
8148
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Submitting scale ladder', {
7247
8149
  symbol: params.symbol,
@@ -7254,50 +8156,81 @@ async function _HyperLiquidProvider_placeScaleOrder(params, context) {
7254
8156
  const result = await exchangeClient.order({
7255
8157
  orders,
7256
8158
  grouping: 'na',
7257
- builder: await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this),
8159
+ ...(builder && { builder }),
7258
8160
  });
7259
- if (result.status !== 'ok') {
7260
- throw new Error(`Scale order failed: ${JSON.stringify(result)}`);
7261
- }
7262
8161
  const statuses = result.response?.data?.statuses ?? [];
7263
- // Which rung each ID came from matters: `submittedSize` has to add up the
7264
- // slices that actually rested, not the total that was asked for.
7265
- const restedRungs = statuses
7266
- .map((status, index) => ({
7267
- orderId: __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderIdFromStatus).call(this, status),
7268
- size: sizes[index],
7269
- }))
7270
- .filter((rung) => rung.orderId !== undefined);
7271
- // A ladder that came back with no IDs at all rested nothing; reporting it
7272
- // as a success would hand the caller a handle that cancels nothing.
7273
- if (restedRungs.length === 0) {
7274
- throw new Error(`Scale order rejected: ${JSON.stringify(result.response?.data)}`);
7275
- }
7276
- const childOrderIds = restedRungs.map((rung) => rung.orderId);
7277
- const groupId = (0, idUtils_js_1.generatePerpsId)('scale');
7278
- __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").set(groupId, { assetId, orderIds: childOrderIds });
7279
- // The batch is not atomic, so a ladder can come back part-rested. Reporting
7280
- // the requested total as submitted would tell the caller they are exposed
7281
- // for more than they are.
7282
- const isPartial = restedRungs.length < count;
7283
- if (isPartial) {
7284
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Scale ladder only partly rested', {
7285
- groupId,
7286
- rested: restedRungs.length,
8162
+ const outcomes = statuses
8163
+ .slice(0, count)
8164
+ .map((status) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderPlacementOutcome).call(this, status));
8165
+ const acceptedCount = outcomes.filter((outcome) => outcome !== undefined).length;
8166
+ const restingChildOrderIds = outcomes.flatMap((outcome) => outcome?.state === 'resting' ? [outcome.orderId] : []);
8167
+ const filledChildOrderIds = outcomes.flatMap((outcome) => outcome?.state === 'filled' ? [outcome.orderId] : []);
8168
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
8169
+ const remainingOrderIds = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequests).call(this, exchangeClient, restingChildOrderIds.map((orderId) => ({
8170
+ a: assetId,
8171
+ o: Number(orderId),
8172
+ })));
8173
+ const recoverableOrderIds = [
8174
+ ...filledChildOrderIds,
8175
+ ...remainingOrderIds.map(String),
8176
+ ];
8177
+ return (0, hyperLiquidValidation_js_1.createErrorResult)(new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE), {
8178
+ success: false,
8179
+ submittedSize: formattedSize,
8180
+ ...(recoverableOrderIds.length > 0 && {
8181
+ childOrderIds: recoverableOrderIds,
8182
+ }),
8183
+ });
8184
+ }
8185
+ if (result.status !== 'ok' ||
8186
+ statuses.length !== count ||
8187
+ acceptedCount !== count) {
8188
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Scale ladder was not fully accepted', {
8189
+ accepted: acceptedCount,
8190
+ filled: filledChildOrderIds.length,
8191
+ resting: restingChildOrderIds.length,
7287
8192
  requested: count,
7288
8193
  statuses,
7289
8194
  });
8195
+ const remainingOrderIds = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequests).call(this, exchangeClient, restingChildOrderIds.map((orderId) => ({
8196
+ a: assetId,
8197
+ o: Number(orderId),
8198
+ })));
8199
+ const recoverableOrderIds = [
8200
+ ...filledChildOrderIds,
8201
+ ...remainingOrderIds.map(String),
8202
+ ];
8203
+ if (remainingOrderIds.length > 0) {
8204
+ const remainingRestingOrderIds = remainingOrderIds.map(String);
8205
+ __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").set(groupId, {
8206
+ symbol: params.symbol,
8207
+ orderIds: remainingRestingOrderIds,
8208
+ });
8209
+ return (0, hyperLiquidValidation_js_1.createErrorResult)(new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_STRATEGY_CANCEL_INCOMPLETE), {
8210
+ success: false,
8211
+ orderId: groupId,
8212
+ childOrderIds: recoverableOrderIds,
8213
+ submittedSize: formattedSize,
8214
+ });
8215
+ }
8216
+ if (recoverableOrderIds.length > 0) {
8217
+ return (0, hyperLiquidValidation_js_1.createErrorResult)(new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_REJECTED), {
8218
+ success: false,
8219
+ childOrderIds: recoverableOrderIds,
8220
+ submittedSize: formattedSize,
8221
+ });
8222
+ }
8223
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_REJECTED);
7290
8224
  }
8225
+ __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").set(groupId, {
8226
+ symbol: params.symbol,
8227
+ orderIds: restingChildOrderIds,
8228
+ });
7291
8229
  return {
7292
8230
  success: true,
7293
8231
  orderId: groupId,
7294
- childOrderIds,
7295
- submittedSize: isPartial
7296
- ? (0, hyperLiquidAdapter_js_1.formatHyperLiquidSize)({
7297
- size: restedRungs.reduce((total, rung) => total + parseFloat(rung.size), 0),
7298
- szDecimals,
7299
- })
7300
- : formattedSize,
8232
+ childOrderIds: restingChildOrderIds,
8233
+ submittedSize: formattedSize,
7301
8234
  };
7302
8235
  }, _HyperLiquidProvider_startChaseSession =
7303
8236
  /**
@@ -7312,12 +8245,12 @@ async function _HyperLiquidProvider_placeScaleOrder(params, context) {
7312
8245
  * @returns A promise that resolves to the result.
7313
8246
  */
7314
8247
  async function _HyperLiquidProvider_startChaseSession(params, context, generation) {
7315
- const { assetId, szDecimals, formattedSize } = context;
8248
+ const { assetId, szDecimals, formattedSize, builder } = context;
7316
8249
  // The preamble is several round trips long. A disconnect during it has
7317
8250
  // already torn down everything this session would run on, so the chase
7318
8251
  // stops here rather than reading the book and putting a fresh order on it
7319
8252
  // for a provider that no longer exists.
7320
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
8253
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7321
8254
  throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7322
8255
  }
7323
8256
  let quotePrice = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getChaseQuotePrice).call(this, {
@@ -7325,8 +8258,9 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7325
8258
  isBuy: params.isBuy,
7326
8259
  szDecimals,
7327
8260
  });
7328
- if (quotePrice === 'gone') {
7329
- // No own order to be gone on a first placement; narrows the union.
8261
+ if (quotePrice === CHASE_ORDER_STATUS_UNAVAILABLE ||
8262
+ typeof quotePrice !== 'string') {
8263
+ // A first placement has no child whose terminal state could be read.
7330
8264
  throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_CHASE_TOUCH_UNAVAILABLE);
7331
8265
  }
7332
8266
  // The book read is a round trip of its own, so the check above does not
@@ -7335,12 +8269,7 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7335
8269
  // before the next one, leaving one window that cannot be closed from here
7336
8270
  // — a disconnect arriving while the submission itself is in flight, which
7337
8271
  // the check after it handles.
7338
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
7339
- throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7340
- }
7341
- // Read once, while the caller's fee-source context is still set.
7342
- const builder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderOrderContext).call(this);
7343
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
8272
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7344
8273
  throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7345
8274
  }
7346
8275
  // Held onto rather than looked up again after the submission returns.
@@ -7358,8 +8287,7 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7358
8287
  price: quotePrice,
7359
8288
  size: formattedSize,
7360
8289
  reduceOnly: params.reduceOnly ?? false,
7361
- builderFee: builder.f,
7362
- builderAddress: builder.b,
8290
+ builder,
7363
8291
  exchangeClient: placingClient,
7364
8292
  });
7365
8293
  break;
@@ -7369,7 +8297,7 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7369
8297
  !isRetryableChasePlacementError(error)) {
7370
8298
  throw error;
7371
8299
  }
7372
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
8300
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7373
8301
  throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7374
8302
  }
7375
8303
  const refreshedQuote = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getChaseQuotePrice).call(this, {
@@ -7377,10 +8305,11 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7377
8305
  isBuy: params.isBuy,
7378
8306
  szDecimals,
7379
8307
  });
7380
- if (refreshedQuote === 'gone') {
8308
+ if (refreshedQuote === CHASE_ORDER_STATUS_UNAVAILABLE ||
8309
+ typeof refreshedQuote !== 'string') {
7381
8310
  throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_CHASE_TOUCH_UNAVAILABLE);
7382
8311
  }
7383
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
8312
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7384
8313
  throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_CHASE_ABANDONED);
7385
8314
  }
7386
8315
  quotePrice = refreshedQuote;
@@ -7408,11 +8337,11 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7408
8337
  maxDistanceBps: params.chaseMaxDistanceBps,
7409
8338
  intervalMs,
7410
8339
  lastSnapshotSizeRefreshAt: 0,
7411
- builderFee: builder.f,
7412
- builderAddress: builder.b,
7413
- deadline: Date.now() +
7414
- (params.chaseMaxDurationMs ?? perpsConfig_js_1.CHASE_ORDER_CONFIG.DefaultMaxDurationMs),
7415
- maxRepricings: params.chaseMaxRepricings ?? perpsConfig_js_1.CHASE_ORDER_CONFIG.DefaultMaxRepricings,
8340
+ builder,
8341
+ deadline: params.chaseMaxDurationMs === undefined
8342
+ ? Number.POSITIVE_INFINITY
8343
+ : Date.now() + params.chaseMaxDurationMs,
8344
+ maxRepricings: params.chaseMaxRepricings ?? Number.POSITIVE_INFINITY,
7416
8345
  repricings: 0,
7417
8346
  timer: null,
7418
8347
  active: true,
@@ -7431,7 +8360,7 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7431
8360
  // and the failure is then true of the venue as well as of this provider.
7432
8361
  // Best-effort still: the transport underneath that client may already be
7433
8362
  // closing, so a cancel that does not land falls back to reporting the id.
7434
- if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_chaseGeneration, "f")) {
8363
+ if (generation !== __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f")) {
7435
8364
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase placement outlived its provider', {
7436
8365
  orderId,
7437
8366
  restingPrice: quotePrice,
@@ -7443,7 +8372,9 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7443
8372
  // that is no longer on the book would send the caller to cancel
7444
8373
  // something already gone; naming one that still rests is the only
7445
8374
  // route left to it.
7446
- ...(outcome === 'refused' ? { childOrderIds: [orderId] } : {}),
8375
+ ...(outcome === CancelChildOutcome.Refused
8376
+ ? { childOrderIds: [orderId] }
8377
+ : {}),
7447
8378
  submittedSize: formattedSize,
7448
8379
  });
7449
8380
  }
@@ -7479,8 +8410,8 @@ async function _HyperLiquidProvider_startChaseSession(params, context, generatio
7479
8410
  * @param params.own.orderId - Exchange ID of that order.
7480
8411
  * @param params.own.price - Price that order rests at.
7481
8412
  * @param params.own.size - Size it was last placed for.
7482
- * @returns The formatted price the chase should rest at, or `'gone'` when the
7483
- * order it was chasing is no longer live.
8413
+ * @returns The formatted price, a terminal child result, or the status
8414
+ * unavailable sentinel when the child may still be live.
7484
8415
  */
7485
8416
  async function _HyperLiquidProvider_getChaseQuotePrice(params) {
7486
8417
  const { symbol, isBuy, szDecimals, own } = params;
@@ -7495,8 +8426,9 @@ async function _HyperLiquidProvider_getChaseQuotePrice(params) {
7495
8426
  callerOrderId: own?.orderId,
7496
8427
  ownSide,
7497
8428
  });
7498
- if (netting === 'gone') {
7499
- return 'gone';
8429
+ if (netting === CHASE_ORDER_STATUS_UNAVAILABLE ||
8430
+ !(netting instanceof Map)) {
8431
+ return netting;
7500
8432
  }
7501
8433
  const bestBid = readBestExternalPrice(book?.levels?.[0], netting);
7502
8434
  const bestAsk = readBestExternalPrice(book?.levels?.[1], netting);
@@ -7514,9 +8446,7 @@ async function _HyperLiquidProvider_getChaseQuotePrice(params) {
7514
8446
  * @param params.price - Formatted limit price.
7515
8447
  * @param params.size - Formatted size.
7516
8448
  * @param params.reduceOnly - Whether the order may only reduce a position.
7517
- * @param params.builderFee - Builder fee, in tenths of a basis point, captured
7518
- * when the session started so replacements keep the rate they were quoted at.
7519
- * @param params.builderAddress - Builder address captured with the fee.
8449
+ * @param params.builder - Builder context captured when the session started.
7520
8450
  * @param params.exchangeClient - Client to submit through. Passed in rather
7521
8451
  * than looked up here so a first placement can keep the instance it signed
7522
8452
  * with, which is the only one that can take the order back once `disconnect`
@@ -7538,10 +8468,7 @@ async function _HyperLiquidProvider_restChaseOrder(params) {
7538
8468
  },
7539
8469
  ],
7540
8470
  grouping: 'na',
7541
- builder: {
7542
- b: params.builderAddress,
7543
- f: params.builderFee,
7544
- },
8471
+ ...(params.builder && { builder: params.builder }),
7545
8472
  });
7546
8473
  if (result.status !== 'ok') {
7547
8474
  throw new Error(`Chase order failed: ${JSON.stringify(result)}`);
@@ -7553,25 +8480,21 @@ async function _HyperLiquidProvider_restChaseOrder(params) {
7553
8480
  return orderId;
7554
8481
  }, _HyperLiquidProvider_scheduleChaseTick = function _HyperLiquidProvider_scheduleChaseTick(sessionId) {
7555
8482
  const session = __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").get(sessionId);
7556
- if (!session?.active) {
8483
+ if (!session?.active || session.timer !== null) {
7557
8484
  return;
7558
8485
  }
7559
8486
  const timer = setTimeout(() => {
7560
8487
  session.timer = null;
7561
- __classPrivateFieldSet(this, _HyperLiquidProvider_chaseTickQueue, __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f").then(async () => {
7562
- // A rejected tick must not stop the session from being cancellable, and
7563
- // must not surface as an unhandled rejection either.
7564
- try {
7565
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_runChaseTick).call(this, sessionId);
7566
- }
7567
- catch (error) {
7568
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase tick failed', {
7569
- sessionId,
7570
- error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.chaseTick').message,
7571
- });
7572
- __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_recoverFailedChaseTick).call(this, sessionId);
7573
- }
7574
- return undefined;
8488
+ __classPrivateFieldSet(this, _HyperLiquidProvider_chaseTickQueue, __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f")
8489
+ .then(() => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_runChaseTick).call(this, sessionId))
8490
+ .catch((error) => {
8491
+ // Resolve the shared queue after every failure. Otherwise one
8492
+ // rejected tick prevents all later ticks and teardown from running.
8493
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase tick failed', {
8494
+ sessionId,
8495
+ error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.chaseTick').message,
8496
+ });
8497
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_recoverFailedChaseTick).call(this, sessionId);
7575
8498
  }), "f");
7576
8499
  }, session.intervalMs);
7577
8500
  // Node keeps the process alive for a pending timer; a background chase is
@@ -7603,6 +8526,12 @@ async function _HyperLiquidProvider_restChaseOrder(params) {
7603
8526
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase ended with nothing resting', {
7604
8527
  sessionId,
7605
8528
  });
8529
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session).catch((error) => {
8530
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase collateral cleanup failed', {
8531
+ sessionId,
8532
+ error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.recoverFailedChaseTick').message,
8533
+ });
8534
+ });
7606
8535
  return;
7607
8536
  }
7608
8537
  __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_scheduleChaseTick).call(this, sessionId);
@@ -7636,6 +8565,7 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7636
8565
  session.active = false;
7637
8566
  session.status = perpsConfig_js_1.CHASE_ORDER_STATUS.DurationReached;
7638
8567
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase session window closed', { sessionId });
8568
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
7639
8569
  return;
7640
8570
  }
7641
8571
  const rawQuotePrice = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getChaseQuotePrice).call(this, {
@@ -7650,16 +8580,29 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7650
8580
  }
7651
8581
  : undefined,
7652
8582
  });
7653
- if (rawQuotePrice === 'gone') {
7654
- // The order left the book without the loop seeing it — it filled. Ending
7655
- // here releases its slot against the venue's concurrency cap instead of
7656
- // holding one until the window closes.
8583
+ if (typeof rawQuotePrice !== 'string') {
8584
+ // The order left the book without the loop seeing it. Preserve the venue
8585
+ // status so an external cancellation is not reported as a fill.
7657
8586
  session.orderId = null;
7658
8587
  session.active = false;
7659
- session.status = perpsConfig_js_1.CHASE_ORDER_STATUS.Filled;
7660
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order filled between ticks', {
8588
+ session.status = rawQuotePrice.status;
8589
+ session.size = rawQuotePrice.remainingSize ?? '0';
8590
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order ended between ticks', {
8591
+ sessionId,
8592
+ status: session.status,
8593
+ });
8594
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
8595
+ return;
8596
+ }
8597
+ if (rawQuotePrice === CHASE_ORDER_STATUS_UNAVAILABLE) {
8598
+ // The child may still be live. Leave it untouched and retry on the next
8599
+ // normal interval instead of permanently stranding the Chase after one
8600
+ // short status outage.
8601
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order status unavailable', {
7661
8602
  sessionId,
8603
+ orderId: session.orderId,
7662
8604
  });
8605
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_scheduleChaseTick).call(this, sessionId);
7663
8606
  return;
7664
8607
  }
7665
8608
  const arrivalPrice = Number.parseFloat(session.arrivalPrice);
@@ -7717,12 +8660,12 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7717
8660
  // A refused cancel leaves the child resting. Preserve its ID so the
7718
8661
  // caller that stopped this session can report an incomplete cancel and
7719
8662
  // retry it instead of orphaning the order.
7720
- if (outcome !== 'refused') {
8663
+ if (outcome !== CancelChildOutcome.Refused) {
7721
8664
  session.orderId = null;
7722
8665
  }
7723
8666
  return;
7724
8667
  }
7725
- if (outcome === 'refused') {
8668
+ if (outcome === CancelChildOutcome.Refused) {
7726
8669
  // The exchange kept the order, so it is still resting at the old price.
7727
8670
  // Placing the replacement anyway would double the position. Leave it
7728
8671
  // alone and try again on the next tick, which is why this falls through
@@ -7733,25 +8676,36 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7733
8676
  });
7734
8677
  reachedMaxDistance = false;
7735
8678
  }
7736
- else if (outcome === 'gone') {
8679
+ else if (outcome === CancelChildOutcome.Gone) {
7737
8680
  // The order left the book between ticks: it filled, or something else
7738
8681
  // cancelled it. There is nothing left to chase.
7739
8682
  const goneOrderId = session.orderId;
7740
- const remaining = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, goneOrderId);
7741
8683
  session.orderId = null;
7742
8684
  session.active = false;
7743
- session.status =
7744
- remaining === null
7745
- ? perpsConfig_js_1.CHASE_ORDER_STATUS.Filled
7746
- : perpsConfig_js_1.CHASE_ORDER_STATUS.Failed;
7747
- if (remaining !== null) {
7748
- session.size = remaining;
8685
+ let remainder;
8686
+ try {
8687
+ remainder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, goneOrderId, CHASE_ORDER_STATUS_RETRY_COUNT);
8688
+ }
8689
+ catch (error) {
8690
+ session.status = perpsConfig_js_1.CHASE_ORDER_STATUS.Failed;
8691
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order status unavailable', {
8692
+ sessionId,
8693
+ orderId: goneOrderId,
8694
+ error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.chaseGoneOrderStatus').message,
8695
+ });
8696
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
8697
+ return;
8698
+ }
8699
+ session.status = remainder.terminalStatus ?? perpsConfig_js_1.CHASE_ORDER_STATUS.Failed;
8700
+ if (remainder.remainingSize !== null) {
8701
+ session.size = remainder.remainingSize;
7749
8702
  }
7750
8703
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order no longer resting', {
7751
8704
  sessionId,
7752
8705
  status: session.status,
7753
8706
  remainingSize: session.size,
7754
8707
  });
8708
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
7755
8709
  return;
7756
8710
  }
7757
8711
  else {
@@ -7765,25 +8719,44 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7765
8719
  // No further fills can reach a cancelled order, so what it did not fill
7766
8720
  // is now fixed. Sampling before the cancel would have left a window in
7767
8721
  // which a fill lands and is then re-placed.
7768
- const remaining = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, cancelledOrderId);
8722
+ let remainder;
8723
+ try {
8724
+ remainder = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, cancelledOrderId, CHASE_ORDER_STATUS_RETRY_COUNT);
8725
+ }
8726
+ catch (error) {
8727
+ session.replacingOrderId = null;
8728
+ throw error;
8729
+ }
7769
8730
  // Another round trip, another window for a cancel to land. By now the
7770
8731
  // old order is already off the book, so a cancel that arrived during it
7771
8732
  // found nothing to cancel and reported success — resting a replacement
7772
8733
  // after that would put an order on the book the caller believes is gone
7773
8734
  // and has no handle for.
7774
8735
  if (!session.active || !__classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").has(sessionId)) {
8736
+ session.replacingOrderId = null;
7775
8737
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase cancelled mid-reprice', {
7776
8738
  sessionId,
7777
8739
  });
7778
8740
  return;
7779
8741
  }
7780
- if (remaining === null) {
7781
- // It filled completely between ticks; there is nothing left to chase.
8742
+ if (remainder.remainingSize === null ||
8743
+ (remainder.terminalStatus !== null &&
8744
+ remainder.terminalStatus !== perpsConfig_js_1.CHASE_ORDER_STATUS.Canceled)) {
8745
+ // Nothing is left to chase. Preserve whether the venue actually
8746
+ // confirmed a fill rather than labelling every zero remainder filled.
8747
+ session.replacingOrderId = null;
7782
8748
  session.active = false;
7783
- session.status = perpsConfig_js_1.CHASE_ORDER_STATUS.Filled;
7784
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order fully filled', { sessionId });
8749
+ session.size = remainder.remainingSize ?? '0';
8750
+ session.status =
8751
+ remainder.terminalStatus ?? perpsConfig_js_1.CHASE_ORDER_STATUS.Failed;
8752
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase order has no remainder', {
8753
+ sessionId,
8754
+ status: session.status,
8755
+ });
8756
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
7785
8757
  return;
7786
8758
  }
8759
+ const remaining = remainder.remainingSize;
7787
8760
  // Published *before* the placement starts, not from the promise it
7788
8761
  // returns: the window opens the moment `#restChaseOrder` is entered, so
7789
8762
  // deriving the marker from its return value would leave the synchronous
@@ -7802,8 +8775,7 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7802
8775
  price: quotePrice,
7803
8776
  size: remaining,
7804
8777
  reduceOnly: session.reduceOnly,
7805
- builderFee: session.builderFee,
7806
- builderAddress: session.builderAddress,
8778
+ builder: session.builder,
7807
8779
  // A running session is on a live provider, so the current client is
7808
8780
  // the right one; only the first placement has a teardown to survive.
7809
8781
  exchangeClient: __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient(),
@@ -7841,7 +8813,7 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7841
8813
  }
7842
8814
  }
7843
8815
  }
7844
- if (reachedMaxDistance) {
8816
+ if (reachedMaxDistance && session.maxDistanceBps !== undefined) {
7845
8817
  session.active = false;
7846
8818
  session.status = perpsConfig_js_1.CHASE_ORDER_STATUS.MaxDistanceReached;
7847
8819
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase max distance reached', {
@@ -7849,12 +8821,24 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7849
8821
  restingPrice: session.restingPrice,
7850
8822
  maxDistanceBps: session.maxDistanceBps,
7851
8823
  });
8824
+ __classPrivateFieldGet(this, _HyperLiquidProvider_onChaseOrderMaxDistanceReached, "f")?.call(this, {
8825
+ handle: sessionId,
8826
+ symbol: session.symbol,
8827
+ side: session.isBuy ? 'buy' : 'sell',
8828
+ restingOrderId: session.orderId,
8829
+ restingPrice: session.restingPrice,
8830
+ maxDistanceBps: session.maxDistanceBps,
8831
+ timestamp: Date.now(),
8832
+ providerId: perpsConfig_js_1.PROVIDER_CONFIG.DefaultProvider,
8833
+ });
8834
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
7852
8835
  return;
7853
8836
  }
7854
8837
  if (session.repricings >= session.maxRepricings) {
7855
8838
  session.active = false;
7856
8839
  session.status = perpsConfig_js_1.CHASE_ORDER_STATUS.RepricingLimitReached;
7857
8840
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase repricing cap reached', { sessionId });
8841
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
7858
8842
  return;
7859
8843
  }
7860
8844
  __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_scheduleChaseTick).call(this, sessionId);
@@ -7877,21 +8861,24 @@ async function _HyperLiquidProvider_runChaseTick(sessionId) {
7877
8861
  * @param params.isBuy - Side being quoted.
7878
8862
  * @param params.callerOrderId - The asking session's order, if it has one.
7879
8863
  * @param params.ownSide - The side of the book those orders rest on.
7880
- * @returns Our resting size at each price, or `'gone'` when the asking
7881
- * session's own order is no longer live.
8864
+ * @returns Our resting size at each price, the asking child's terminal
8865
+ * result, or the unavailable sentinel.
7882
8866
  */
7883
8867
  async function _HyperLiquidProvider_resolveOwnRestingSizes(params) {
7884
8868
  const { symbol, isBuy, callerOrderId, ownSide } = params;
7885
8869
  // Every chase this provider is running on this side, the caller included.
7886
- const ourOrders = [...__classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").values()]
7887
- .filter((session) => session.orderId !== null &&
7888
- session.symbol === symbol &&
7889
- session.isBuy === isBuy)
7890
- .map((session) => ({
7891
- orderId: session.orderId,
7892
- price: session.restingPrice,
7893
- size: session.size,
7894
- }));
8870
+ const ourOrders = [];
8871
+ for (const session of __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").values()) {
8872
+ if (session.orderId !== null &&
8873
+ session.symbol === symbol &&
8874
+ session.isBuy === isBuy) {
8875
+ ourOrders.push({
8876
+ orderId: session.orderId,
8877
+ price: session.restingPrice,
8878
+ size: session.size,
8879
+ });
8880
+ }
8881
+ }
7895
8882
  if (ourOrders.length === 0) {
7896
8883
  return new Map();
7897
8884
  }
@@ -7908,27 +8895,44 @@ async function _HyperLiquidProvider_resolveOwnRestingSizes(params) {
7908
8895
  const level = ownSide?.find((entry) => entry.px === order.price);
7909
8896
  return !level || parseFloat(level.sz) <= (recorded.get(order.price) ?? 0);
7910
8897
  });
7911
- let callerIsGone = false;
7912
- for (const order of ambiguous) {
7913
- try {
7914
- const live = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, order.orderId);
8898
+ let callerTerminalResult = null;
8899
+ let callerStatusUnavailable = false;
8900
+ const resolutions = await Promise.allSettled(ambiguous.map((order) => __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, order.orderId, CHASE_ORDER_STATUS_RETRY_COUNT)));
8901
+ resolutions.forEach((resolution, index) => {
8902
+ const order = ambiguous[index];
8903
+ if (resolution.status === 'fulfilled') {
8904
+ const { remainingSize: live, terminalStatus } = resolution.value;
7915
8905
  const delta = (live === null ? 0 : parseFloat(live)) - parseFloat(order.size);
7916
8906
  recorded.set(order.price, Math.max(0, (recorded.get(order.price) ?? 0) + delta));
7917
- if (live === null && order.orderId === callerOrderId) {
7918
- callerIsGone = true;
8907
+ if (terminalStatus !== null && order.orderId === callerOrderId) {
8908
+ callerTerminalResult = {
8909
+ remainingSize: live,
8910
+ status: terminalStatus,
8911
+ };
7919
8912
  }
7920
8913
  }
7921
- catch (error) {
7922
- // A failed lookup must not stop the chase. Keeping the recorded size can
7923
- // only over-subtract, which quotes a level deeper rather than leaving
7924
- // the order stranded at a stale price.
7925
- __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase own-size lookup failed', {
7926
- orderId: order.orderId,
7927
- error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.chaseOwnSize').message,
7928
- });
8914
+ else {
8915
+ const { reason: error } = resolution;
8916
+ if (error instanceof ChaseOrderStatusUnavailableError &&
8917
+ order.orderId === callerOrderId) {
8918
+ callerStatusUnavailable = true;
8919
+ }
8920
+ else {
8921
+ // A failed lookup must not stop the chase. Keeping the recorded size
8922
+ // can only over-subtract, which quotes a level deeper rather than
8923
+ // leaving the order stranded at a stale price.
8924
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase own-size lookup failed', {
8925
+ orderId: order.orderId,
8926
+ error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.chaseOwnSize')
8927
+ .message,
8928
+ });
8929
+ }
7929
8930
  }
8931
+ });
8932
+ if (callerStatusUnavailable) {
8933
+ return CHASE_ORDER_STATUS_UNAVAILABLE;
7930
8934
  }
7931
- return callerIsGone ? 'gone' : recorded;
8935
+ return callerTerminalResult ?? recorded;
7932
8936
  }, _HyperLiquidProvider_readOrderRemainder =
7933
8937
  /**
7934
8938
  * Read what a chase's just-cancelled order left unfilled.
@@ -7944,19 +8948,51 @@ async function _HyperLiquidProvider_resolveOwnRestingSizes(params) {
7944
8948
  * window in which a fill lands and is then re-placed on top.
7945
8949
  *
7946
8950
  * @param orderId - Exchange ID of the order that was just cancelled.
7947
- * @returns The unfilled size, or null when nothing is left to chase.
8951
+ * @param unknownStatusRetries - Remaining retries for a transient unknown ID.
8952
+ * @returns The unfilled size and exact terminal status, when present.
7948
8953
  */
7949
- async function _HyperLiquidProvider_readOrderRemainder(orderId) {
8954
+ async function _HyperLiquidProvider_readOrderRemainder(orderId, unknownStatusRetries = 0) {
7950
8955
  const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
7951
8956
  const status = await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getInfoClient().orderStatus({
7952
8957
  user: userAddress,
7953
8958
  oid: parseInt(orderId, 10),
7954
8959
  });
7955
8960
  if (status.status !== 'order') {
7956
- return null;
8961
+ // unknownOid can be transient while the order-status index catches up.
8962
+ // Treating it as a fill would drop the only handle to a live order.
8963
+ if (unknownStatusRetries > 0) {
8964
+ await new Promise((resolve) => setTimeout(resolve, CHASE_ORDER_STATUS_RETRY_DELAY_MS));
8965
+ return await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderRemainder).call(this, orderId, unknownStatusRetries - 1);
8966
+ }
8967
+ throw new ChaseOrderStatusUnavailableError();
7957
8968
  }
7958
8969
  const remaining = String(status.order.order.sz);
7959
- return parseFloat(remaining) > 0 ? remaining : null;
8970
+ const remainingSize = parseFloat(remaining) > 0 ? remaining : null;
8971
+ switch (status.order.status) {
8972
+ case 'open':
8973
+ case 'triggered':
8974
+ return { remainingSize, terminalStatus: null };
8975
+ case 'filled':
8976
+ return {
8977
+ remainingSize: null,
8978
+ terminalStatus: perpsConfig_js_1.CHASE_ORDER_STATUS.Filled,
8979
+ };
8980
+ case 'canceled':
8981
+ return remainingSize === null
8982
+ ? {
8983
+ remainingSize: null,
8984
+ terminalStatus: perpsConfig_js_1.CHASE_ORDER_STATUS.Filled,
8985
+ }
8986
+ : {
8987
+ remainingSize,
8988
+ terminalStatus: perpsConfig_js_1.CHASE_ORDER_STATUS.Canceled,
8989
+ };
8990
+ default:
8991
+ return {
8992
+ remainingSize,
8993
+ terminalStatus: perpsConfig_js_1.CHASE_ORDER_STATUS.Failed,
8994
+ };
8995
+ }
7960
8996
  }, _HyperLiquidProvider_cancelChaseChild =
7961
8997
  /**
7962
8998
  * Cancel the order a chase session currently has resting.
@@ -7969,7 +9005,7 @@ async function _HyperLiquidProvider_readOrderRemainder(orderId) {
7969
9005
  */
7970
9006
  async function _HyperLiquidProvider_cancelChaseChild(session, placingClient) {
7971
9007
  if (!session.orderId) {
7972
- return 'gone';
9008
+ return CancelChildOutcome.Gone;
7973
9009
  }
7974
9010
  // Looked up only once there is something to cancel, so a session with
7975
9011
  // nothing resting still answers on a provider whose client is already gone.
@@ -7983,7 +9019,7 @@ async function _HyperLiquidProvider_cancelChaseChild(session, placingClient) {
7983
9019
  catch (error) {
7984
9020
  const message = (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.cancelChaseChild').message.toLowerCase();
7985
9021
  if (ALREADY_GONE_CANCEL_MARKERS.some((marker) => message.includes(marker))) {
7986
- return 'gone';
9022
+ return CancelChildOutcome.Gone;
7987
9023
  }
7988
9024
  throw error;
7989
9025
  }
@@ -8018,19 +9054,194 @@ async function _HyperLiquidProvider_retractOrphanedChaseOrder(session, placingCl
8018
9054
  error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.startChaseSession')
8019
9055
  .message,
8020
9056
  });
8021
- return 'refused';
9057
+ return CancelChildOutcome.Refused;
8022
9058
  }
8023
9059
  }, _HyperLiquidProvider_stopChaseSession = function _HyperLiquidProvider_stopChaseSession(sessionId) {
8024
9060
  const session = __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").get(sessionId);
8025
9061
  if (!session) {
8026
9062
  return;
8027
9063
  }
9064
+ const wasActive = session.active;
8028
9065
  session.active = false;
8029
- session.status = perpsConfig_js_1.CHASE_ORDER_STATUS.TerminationPending;
9066
+ if (wasActive) {
9067
+ session.status = perpsConfig_js_1.CHASE_ORDER_STATUS.TerminationPending;
9068
+ }
8030
9069
  if (session.timer) {
8031
9070
  clearTimeout(session.timer);
8032
9071
  session.timer = null;
8033
9072
  }
9073
+ }, _HyperLiquidProvider_rebalanceChaseSession =
9074
+ /**
9075
+ * Rebalance one Chase session at most once at a time. Failed cleanup keeps
9076
+ * the transfer context so a later lifecycle read or cancel can retry it.
9077
+ *
9078
+ * @param session - Chase session whose manual HIP-3 transfer is complete.
9079
+ * @returns Whether no collateral cleanup remains for the session.
9080
+ */
9081
+ async function _HyperLiquidProvider_rebalanceChaseSession(session) {
9082
+ const { hip3Transfer } = session;
9083
+ if (!hip3Transfer) {
9084
+ return true;
9085
+ }
9086
+ if (session.orderId !== null ||
9087
+ session.replacingOrderId !== null ||
9088
+ session.pendingReplacement !== null) {
9089
+ return false;
9090
+ }
9091
+ let rebalancePromise = session.hip3RebalancePromise;
9092
+ if (!rebalancePromise) {
9093
+ rebalancePromise = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3PostOrderRebalance).call(this, hip3Transfer);
9094
+ session.hip3RebalancePromise = rebalancePromise;
9095
+ }
9096
+ try {
9097
+ const success = await rebalancePromise;
9098
+ if (success && session.hip3Transfer === hip3Transfer) {
9099
+ delete session.hip3Transfer;
9100
+ }
9101
+ return success;
9102
+ }
9103
+ finally {
9104
+ if (session.hip3RebalancePromise === rebalancePromise) {
9105
+ delete session.hip3RebalancePromise;
9106
+ }
9107
+ }
9108
+ }, _HyperLiquidProvider_adaptTwapOrder = function _HyperLiquidProvider_adaptTwapOrder(params) {
9109
+ const { historyEntry, now } = params;
9110
+ const { state, twapId } = historyEntry;
9111
+ if (twapId === undefined || !Number.isSafeInteger(twapId) || twapId < 0) {
9112
+ return null;
9113
+ }
9114
+ const totalSize = new bignumber_js_1.BigNumber(state.sz);
9115
+ const executedSize = new bignumber_js_1.BigNumber(state.executedSz);
9116
+ const executedNotional = new bignumber_js_1.BigNumber(state.executedNtl);
9117
+ if (!totalSize.isFinite() ||
9118
+ !executedSize.isFinite() ||
9119
+ !executedNotional.isFinite() ||
9120
+ !Number.isFinite(state.timestamp) ||
9121
+ !Number.isFinite(historyEntry.time) ||
9122
+ !Number.isFinite(state.minutes)) {
9123
+ return null;
9124
+ }
9125
+ let boundedExecutedSize = executedSize;
9126
+ if (executedSize.isLessThan(0)) {
9127
+ boundedExecutedSize = new bignumber_js_1.BigNumber(0);
9128
+ }
9129
+ else if (executedSize.isGreaterThan(totalSize)) {
9130
+ boundedExecutedSize = totalSize;
9131
+ }
9132
+ const rawRemainingSize = totalSize.minus(boundedExecutedSize);
9133
+ const remainingSize = rawRemainingSize.isGreaterThan(0)
9134
+ ? rawRemainingSize
9135
+ : new bignumber_js_1.BigNumber(0);
9136
+ const status = resolveTwapOrderStatus(historyEntry, boundedExecutedSize, totalSize);
9137
+ const fills = params.sliceFills
9138
+ .map(adaptTwapOrderFill)
9139
+ .sort((left, right) => left.timestamp - right.timestamp);
9140
+ const startedAt = state.timestamp;
9141
+ const historyUpdatedAt = normalizeTwapHistoryTimestamp(historyEntry.time);
9142
+ const lastUpdated = Math.max(startedAt, historyUpdatedAt, ...fills.map((fill) => fill.timestamp));
9143
+ const durationMilliseconds = state.minutes * MILLISECONDS_PER_MINUTE;
9144
+ const elapsedTimeMilliseconds = Math.min(durationMilliseconds, Math.max(0, (status === PerpsTwapLifecycleStatus.Active ? now : lastUpdated) -
9145
+ startedAt));
9146
+ const fillProgressBps = totalSize.isGreaterThan(0)
9147
+ ? boundedExecutedSize
9148
+ .dividedBy(totalSize)
9149
+ .multipliedBy(hyperLiquidConfig_js_1.BASIS_POINTS_DIVISOR)
9150
+ .integerValue(bignumber_js_1.BigNumber.ROUND_FLOOR)
9151
+ .toNumber()
9152
+ : 0;
9153
+ const timeProgressBps = durationMilliseconds > 0
9154
+ ? Math.floor((elapsedTimeMilliseconds / durationMilliseconds) *
9155
+ hyperLiquidConfig_js_1.BASIS_POINTS_DIVISOR)
9156
+ : 0;
9157
+ return {
9158
+ orderId: twapId.toString(),
9159
+ symbol: state.coin,
9160
+ side: state.side === 'B' ? 'buy' : 'sell',
9161
+ size: state.sz,
9162
+ executedSize: boundedExecutedSize.toString(),
9163
+ remainingSize: remainingSize.toFixed(),
9164
+ executedNotional: state.executedNtl,
9165
+ ...(executedSize.isGreaterThan(0) && {
9166
+ averagePrice: executedNotional.dividedBy(executedSize).toFixed(),
9167
+ }),
9168
+ fillProgressBps,
9169
+ timeProgressBps,
9170
+ elapsedTimeMilliseconds,
9171
+ durationMinutes: state.minutes,
9172
+ randomize: state.randomize,
9173
+ reduceOnly: state.reduceOnly,
9174
+ status,
9175
+ startedAt,
9176
+ lastUpdated,
9177
+ ...(historyEntry.status.status ===
9178
+ HyperLiquidTwapLifecycleStatus.Failed && {
9179
+ error: historyEntry.status.description,
9180
+ }),
9181
+ fills,
9182
+ };
9183
+ }, _HyperLiquidProvider_rebalanceTrackedTwapOrder =
9184
+ /**
9185
+ * Rebalance one tracked TWAP at most once at a time. A failed transfer keeps
9186
+ * the tracking entry so the next lifecycle read or cancel can retry it.
9187
+ *
9188
+ * @param trackingKey - Account-scoped TWAP tracking key.
9189
+ * @returns Whether no cleanup remains for the entry.
9190
+ */
9191
+ async function _HyperLiquidProvider_rebalanceTrackedTwapOrder(trackingKey) {
9192
+ const trackedOrder = __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey);
9193
+ if (!trackedOrder) {
9194
+ return true;
9195
+ }
9196
+ if (!trackedOrder.hip3Transfer) {
9197
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey) === trackedOrder) {
9198
+ __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").delete(trackingKey);
9199
+ }
9200
+ return true;
9201
+ }
9202
+ let { rebalancePromise } = trackedOrder;
9203
+ if (!rebalancePromise) {
9204
+ rebalancePromise = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_handleHip3PostOrderRebalance).call(this, trackedOrder.hip3Transfer);
9205
+ trackedOrder.rebalancePromise = rebalancePromise;
9206
+ }
9207
+ try {
9208
+ const success = await rebalancePromise;
9209
+ if (success &&
9210
+ __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey) === trackedOrder) {
9211
+ __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").delete(trackingKey);
9212
+ }
9213
+ return success;
9214
+ }
9215
+ finally {
9216
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey) === trackedOrder &&
9217
+ trackedOrder.rebalancePromise === rebalancePromise) {
9218
+ delete trackedOrder.rebalancePromise;
9219
+ }
9220
+ }
9221
+ }, _HyperLiquidProvider_rebalanceTerminalHip3Twaps =
9222
+ /**
9223
+ * Return manually moved HIP-3 collateral after a TWAP reaches a terminal
9224
+ * venue state. The scoped registry survives reconnects so an active schedule
9225
+ * cannot lose its eventual cleanup path.
9226
+ *
9227
+ * @param orders - Current and historical TWAP lifecycle records.
9228
+ * @param scope - Current account and network identity.
9229
+ */
9230
+ async function _HyperLiquidProvider_rebalanceTerminalHip3Twaps(orders, scope) {
9231
+ for (const order of orders) {
9232
+ if (order.status === PerpsTwapLifecycleStatus.Active) {
9233
+ continue;
9234
+ }
9235
+ const trackingKey = getTwapOrderScopeKey({
9236
+ ...scope,
9237
+ orderId: order.orderId,
9238
+ });
9239
+ const trackedOrder = __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey);
9240
+ if (trackedOrder?.symbol !== order.symbol) {
9241
+ continue;
9242
+ }
9243
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceTrackedTwapOrder).call(this, trackingKey);
9244
+ }
8034
9245
  }, _HyperLiquidProvider_cancelStrategyOrder =
8035
9246
  /**
8036
9247
  * Cancel a strategy placement by its handle.
@@ -8077,21 +9288,64 @@ async function _HyperLiquidProvider_cancelTwapOrder(params) {
8077
9288
  if (!coinValidation.isValid) {
8078
9289
  throw new Error(coinValidation.error);
8079
9290
  }
8080
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
9291
+ const twapId = Number(params.orderId);
9292
+ if (!/^\d+$/u.test(params.orderId) || !Number.isSafeInteger(twapId)) {
9293
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
9294
+ }
9295
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
9296
+ const network = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode() ? 'testnet' : 'mainnet';
9297
+ const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
9298
+ const trackingKey = getTwapOrderScopeKey({
9299
+ network,
9300
+ userAddress,
9301
+ orderId: params.orderId,
9302
+ });
9303
+ const trackedOrder = __classPrivateFieldGet(this, _HyperLiquidProvider_trackedTwapOrders, "f").get(trackingKey);
9304
+ let ownershipAuthenticated = trackedOrder !== undefined;
9305
+ if (trackedOrder) {
9306
+ if (trackedOrder.symbol !== params.symbol) {
9307
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
9308
+ }
9309
+ }
9310
+ else {
9311
+ let venueOrders = null;
9312
+ try {
9313
+ venueOrders = await this.getTwapOrders();
9314
+ }
9315
+ catch (error) {
9316
+ // History is an ownership check, not the cancellation transport. A
9317
+ // transient read failure must not make a live TWAP impossible to stop.
9318
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('TWAP history unavailable before cancellation', {
9319
+ orderId: params.orderId,
9320
+ error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.cancelTwapOrderHistory').message,
9321
+ });
9322
+ }
9323
+ const venueOrder = venueOrders?.find((order) => order.orderId === params.orderId);
9324
+ if (venueOrders !== null && venueOrder?.symbol !== params.symbol) {
9325
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
9326
+ }
9327
+ ownershipAuthenticated = venueOrder !== undefined;
9328
+ }
8081
9329
  const assetId = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
8082
9330
  symbol: params.symbol,
8083
9331
  dexName: (0, hyperLiquidAdapter_js_1.parseAssetName)(params.symbol).dex,
8084
9332
  });
8085
9333
  const result = await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient().twapCancel({
8086
9334
  a: assetId,
8087
- t: parseInt(params.orderId, 10),
9335
+ t: twapId,
8088
9336
  });
8089
9337
  const status = result.response?.data?.status;
8090
- if (status === 'success') {
9338
+ const cancelOutcome = classifyCancelStatus(status);
9339
+ if (cancelOutcome === CancelChildOutcome.Gone && !ownershipAuthenticated) {
9340
+ throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
9341
+ }
9342
+ if (cancelOutcome !== CancelChildOutcome.Refused) {
9343
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceTrackedTwapOrder).call(this, trackingKey);
8091
9344
  return { success: true, orderId: params.orderId };
8092
9345
  }
8093
- const rawError = status?.error ??
8094
- 'TWAP cancellation failed';
9346
+ const rawError = isStatusObject(status) && typeof status.error === 'string'
9347
+ ? status.error
9348
+ : 'TWAP cancellation failed';
8095
9349
  return (0, hyperLiquidValidation_js_1.createErrorResult)(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_mapError).call(this, new Error(rawError)), {
8096
9350
  success: false,
8097
9351
  orderId: params.orderId,
@@ -8108,19 +9362,25 @@ async function _HyperLiquidProvider_cancelScaleOrder(params) {
8108
9362
  if (!group) {
8109
9363
  throw new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
8110
9364
  }
8111
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
8112
- const result = await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient().cancel({
8113
- cancels: group.orderIds.map((orderId) => ({
8114
- a: group.assetId,
8115
- o: parseInt(orderId, 10),
8116
- })),
9365
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
9366
+ const assetId = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getAssetIdWithRepair).call(this, {
9367
+ symbol: group.symbol,
9368
+ dexName: (0, hyperLiquidAdapter_js_1.parseAssetName)(group.symbol).dex,
8117
9369
  });
8118
- const statuses = result.response?.data?.statuses ?? [];
8119
- // A rung that filled or was cancelled individually comes back as a
8120
- // rejection, but nothing of it is resting — counting it as still live would
8121
- // pin the handle open for a ladder that is entirely off the book.
8122
- const remaining = group.orderIds.filter((_unused, index) => classifyCancelStatus(statuses[index]) === 'refused');
9370
+ const exchangeClient = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").getExchangeClient();
9371
+ const cancelRequests = group.orderIds.map((orderId) => ({
9372
+ a: assetId,
9373
+ o: Number(orderId),
9374
+ }));
9375
+ const remaining = (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequests).call(this, exchangeClient, cancelRequests)).map(String);
9376
+ /*
9377
+ * A rung that filled or was cancelled individually comes back as a
9378
+ * rejection, but nothing of it is resting. The shared cancellation helper
9379
+ * distinguishes that result from a refusal while retaining every child
9380
+ * after a malformed or non-ok batch response.
9381
+ */
8123
9382
  if (remaining.length === 0) {
9383
+ __classPrivateFieldGet(this, _HyperLiquidProvider_cancelledScaleOrderGroups, "f").add(params.orderId);
8124
9384
  __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").delete(params.orderId);
8125
9385
  return { success: true, orderId: params.orderId };
8126
9386
  }
@@ -8128,7 +9388,7 @@ async function _HyperLiquidProvider_cancelScaleOrder(params) {
8128
9388
  // handle registered against what is left means the caller can retry the
8129
9389
  // same cancel rather than being told the ladder is gone when it is not.
8130
9390
  __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").set(params.orderId, {
8131
- assetId: group.assetId,
9391
+ symbol: group.symbol,
8132
9392
  orderIds: remaining,
8133
9393
  });
8134
9394
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Scale group cancel left children resting', {
@@ -8189,39 +9449,120 @@ async function _HyperLiquidProvider_performChaseTermination(params) {
8189
9449
  // Nothing rests: the order filled, something else cancelled it, or a
8190
9450
  // replacement failed to go up. Either way the session ends cleanly.
8191
9451
  if (session.orderId === null) {
8192
- __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").delete(params.orderId);
9452
+ session.removeAfterRebalance = true;
9453
+ if (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session)) {
9454
+ __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").delete(params.orderId);
9455
+ }
8193
9456
  return { success: true, orderId: params.orderId };
8194
9457
  }
8195
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this);
9458
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_ensureReadyForTrading).call(this, { requiresBuilderFee: false });
8196
9459
  // Only a refusal leaves an order behind. A child that had already filled or
8197
9460
  // been cancelled is reported as a rejection too, but nothing of it is
8198
9461
  // resting — treating that as a failure would pin the handle open forever on
8199
9462
  // a chase that is entirely finished.
8200
9463
  const cancelOutcome = await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelChaseChild).call(this, session);
8201
- if (cancelOutcome === 'refused') {
9464
+ if (cancelOutcome === CancelChildOutcome.Refused) {
8202
9465
  // The order is still resting. The session stays registered — stopped, but
8203
9466
  // still cancellable — so the caller can retry with the same handle.
8204
9467
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Chase cancel left its order resting', {
8205
9468
  sessionId: params.orderId,
8206
9469
  orderId: session.orderId,
8207
9470
  });
9471
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session);
8208
9472
  return (0, hyperLiquidValidation_js_1.createErrorResult)(new Error(perpsErrorCodes_js_1.PERPS_ERROR_CODES.ORDER_STRATEGY_CANCEL_INCOMPLETE), { success: false, orderId: params.orderId });
8209
9473
  }
8210
- __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").delete(params.orderId);
9474
+ session.orderId = null;
9475
+ session.status = perpsConfig_js_1.CHASE_ORDER_STATUS.Canceled;
9476
+ session.removeAfterRebalance = true;
9477
+ if (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session)) {
9478
+ __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").delete(params.orderId);
9479
+ }
8211
9480
  return { success: true, orderId: params.orderId };
8212
- }, _HyperLiquidProvider_readOrderIdFromStatus = function _HyperLiquidProvider_readOrderIdFromStatus(status) {
9481
+ }, _HyperLiquidProvider_cancelOrderRequests =
9482
+ /**
9483
+ * Cancel a batch and retain every order that may still be live.
9484
+ *
9485
+ * A malformed, truncated, or rejected response cannot prove which requests
9486
+ * reached the venue, so every requested ID remains recoverable for a retry.
9487
+ *
9488
+ * @param exchangeClient - Client that owns the orders.
9489
+ * @param requests - Venue cancel requests.
9490
+ * @returns Order IDs that may still be resting.
9491
+ */
9492
+ async function _HyperLiquidProvider_cancelOrderRequests(exchangeClient, requests) {
9493
+ return (await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cancelOrderRequestBatch).call(this, exchangeClient, requests))
9494
+ .remainingOrderIds;
9495
+ }, _HyperLiquidProvider_cancelOrderRequestBatch =
9496
+ /**
9497
+ * Cancel a batch while distinguishing confirmed cancellations from orders
9498
+ * that were already gone. Replacement rollback may only restore the former.
9499
+ *
9500
+ * @param exchangeClient - Client that owns the orders.
9501
+ * @param requests - Venue cancel requests.
9502
+ * @returns IDs that may still rest and IDs this call confirmed it cancelled.
9503
+ */
9504
+ async function _HyperLiquidProvider_cancelOrderRequestBatch(exchangeClient, requests) {
9505
+ if (requests.length === 0) {
9506
+ return {
9507
+ remainingOrderIds: [],
9508
+ cancelledOrderIds: [],
9509
+ responseComplete: true,
9510
+ };
9511
+ }
9512
+ try {
9513
+ const result = await exchangeClient.cancel({ cancels: requests });
9514
+ const statuses = result.response?.data?.statuses ?? [];
9515
+ if (result.status !== 'ok' || statuses.length !== requests.length) {
9516
+ return {
9517
+ remainingOrderIds: requests.map((request) => request.o),
9518
+ cancelledOrderIds: [],
9519
+ responseComplete: false,
9520
+ };
9521
+ }
9522
+ const remainingOrderIds = [];
9523
+ const cancelledOrderIds = [];
9524
+ requests.forEach((request, index) => {
9525
+ const outcome = classifyCancelStatus(statuses[index]);
9526
+ if (outcome === CancelChildOutcome.Refused) {
9527
+ remainingOrderIds.push(request.o);
9528
+ }
9529
+ else if (outcome === CancelChildOutcome.Cancelled) {
9530
+ cancelledOrderIds.push(request.o);
9531
+ }
9532
+ });
9533
+ return { remainingOrderIds, cancelledOrderIds, responseComplete: true };
9534
+ }
9535
+ catch (error) {
9536
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Order cancellation batch failed', {
9537
+ error: (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.cancelOrderRequests')
9538
+ .message,
9539
+ orderIds: requests.map((request) => request.o),
9540
+ });
9541
+ return {
9542
+ remainingOrderIds: requests.map((request) => request.o),
9543
+ cancelledOrderIds: [],
9544
+ responseComplete: false,
9545
+ };
9546
+ }
9547
+ }, _HyperLiquidProvider_readOrderPlacementOutcome = function _HyperLiquidProvider_readOrderPlacementOutcome(status) {
8213
9548
  if (!isStatusObject(status)) {
8214
9549
  return undefined;
8215
9550
  }
8216
- // `hasProperty` types the property as `unknown`, so each branch is cast to
8217
- // the only shape the exchange puts there.
8218
- const restingOrder = (0, utils_1.hasProperty)(status, 'resting')
8219
- ? status.resting
8220
- : undefined;
8221
- const filledOrder = (0, utils_1.hasProperty)(status, 'filled')
8222
- ? status.filled
8223
- : undefined;
8224
- return (restingOrder?.oid?.toString() ?? filledOrder?.oid?.toString() ?? undefined);
9551
+ for (const state of ['resting', 'filled']) {
9552
+ if (!(0, utils_1.hasProperty)(status, state)) {
9553
+ continue;
9554
+ }
9555
+ const order = status[state];
9556
+ if (isStatusObject(order) &&
9557
+ typeof order.oid === 'number' &&
9558
+ Number.isSafeInteger(order.oid) &&
9559
+ order.oid >= 0) {
9560
+ return { orderId: order.oid.toString(), state };
9561
+ }
9562
+ }
9563
+ return undefined;
9564
+ }, _HyperLiquidProvider_readOrderIdFromStatus = function _HyperLiquidProvider_readOrderIdFromStatus(status) {
9565
+ return __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_readOrderPlacementOutcome).call(this, status)?.orderId;
8225
9566
  }, _HyperLiquidProvider_getDiscountedBuilderFee = function _HyperLiquidProvider_getDiscountedBuilderFee() {
8226
9567
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_userFeeDiscountBips, "f") === undefined) {
8227
9568
  return hyperLiquidConfig_js_1.BUILDER_FEE_CONFIG.MaxFeeTenthsBps;
@@ -8236,15 +9577,14 @@ async function _HyperLiquidProvider_performChaseTermination(params) {
8236
9577
  * cached for this provider/account session. Until then, the ordinary builder
8237
9578
  * and standard fee keep the trade attributable and non-blocking.
8238
9579
  *
9580
+ * @param setupContext - Account, network, and builder approved for the order.
8239
9581
  * @returns HyperLiquid builder address and fee payload.
8240
9582
  */
8241
- async function _HyperLiquidProvider_getBuilderOrderContext() {
8242
- const isTestnet = __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode();
8243
- const network = isTestnet ? 'testnet' : 'mainnet';
8244
- const defaultBuilder = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getBuilderAddress).call(this, isTestnet);
9583
+ async function _HyperLiquidProvider_getBuilderOrderContext(setupContext) {
9584
+ const { network, userAddress, builderAddress: defaultBuilder, } = setupContext;
9585
+ const isTestnet = network === 'testnet';
8245
9586
  if (__classPrivateFieldGet(this, _HyperLiquidProvider_userFeeResolution, "f")?.source === 'subscription') {
8246
9587
  const subscriptionBuilder = __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getSubscriptionBuilderAddress).call(this, isTestnet);
8247
- const userAddress = await __classPrivateFieldGet(this, _HyperLiquidProvider_walletService, "f").getUserAddressWithDefault();
8248
9588
  if (subscriptionBuilder &&
8249
9589
  __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").has(__classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_getApprovedBuilderKey).call(this, network, userAddress, subscriptionBuilder))) {
8250
9590
  return { b: subscriptionBuilder, f: 0 };
@@ -8394,6 +9734,41 @@ async function _HyperLiquidProvider_queryDexPositions(dexName) {
8394
9734
  });
8395
9735
  return { answered: false, positions: [] };
8396
9736
  }
9737
+ }, _HyperLiquidProvider_restoreScaleOrderGroups = function _HyperLiquidProvider_restoreScaleOrderGroups(orders) {
9738
+ const recovered = new Map();
9739
+ for (const order of orders) {
9740
+ if (!order.strategyGroupId) {
9741
+ continue;
9742
+ }
9743
+ const group = recovered.get(order.strategyGroupId) ?? {
9744
+ symbol: order.symbol,
9745
+ orderIds: [],
9746
+ };
9747
+ group.orderIds.push(order.orderId);
9748
+ recovered.set(order.strategyGroupId, group);
9749
+ }
9750
+ for (const [groupId, group] of recovered) {
9751
+ if (__classPrivateFieldGet(this, _HyperLiquidProvider_cancelledScaleOrderGroups, "f").has(groupId)) {
9752
+ continue;
9753
+ }
9754
+ const existingGroup = __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").get(groupId);
9755
+ if (!existingGroup) {
9756
+ __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").set(groupId, group);
9757
+ continue;
9758
+ }
9759
+ if (existingGroup.symbol === group.symbol) {
9760
+ existingGroup.orderIds = [
9761
+ ...new Set([...existingGroup.orderIds, ...group.orderIds]),
9762
+ ];
9763
+ }
9764
+ else {
9765
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('Scale group symbol mismatch during recovery', {
9766
+ groupId,
9767
+ existingSymbol: existingGroup.symbol,
9768
+ recoveredSymbol: group.symbol,
9769
+ });
9770
+ }
9771
+ }
8397
9772
  }, _HyperLiquidProvider_getAllMids =
8398
9773
  /**
8399
9774
  * Get allMids for a DEX — uses WS snapshot as primary source, REST as fallback.
@@ -8410,7 +9785,7 @@ async function _HyperLiquidProvider_getAllMids(infoClient, dexParam) {
8410
9785
  __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('[getMarketDataWithPrices] No WS allMids snapshot, falling back to REST', { dexParam: dexParam ?? 'main' });
8411
9786
  const mids = await infoClient.allMids(dexParam ? { dex: dexParam } : undefined);
8412
9787
  return mids ?? {};
8413
- }, _HyperLiquidProvider_fetchSingleDexFresh = async function _HyperLiquidProvider_fetchSingleDexFresh(infoClient, dex) {
9788
+ }, _HyperLiquidProvider_fetchSingleDexFresh = async function _HyperLiquidProvider_fetchSingleDexFresh(infoClient, dex, lifecycleGeneration) {
8414
9789
  const dexParam = dex ?? '';
8415
9790
  let metaAndCtxs = null;
8416
9791
  try {
@@ -8438,10 +9813,12 @@ async function _HyperLiquidProvider_getAllMids(infoClient, dexParam) {
8438
9813
  allMidsErrorMessage = (0, errorUtils_js_1.ensureError)(error, 'HyperLiquidProvider.getMarketDataWithPrices.allMids').message;
8439
9814
  }
8440
9815
  if (meta?.universe) {
8441
- __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").set(dexParam, meta);
8442
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexMetaCache(dexParam, meta);
8443
- __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").setDexAssetCtxsCache(dexParam, assetCtxs);
8444
- await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_backfillAssetMapForDex).call(this, dex, meta);
9816
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_cacheDexMetadataFromRead).call(this, {
9817
+ dex,
9818
+ meta,
9819
+ assetCtxs,
9820
+ lifecycleGeneration,
9821
+ });
8445
9822
  }
8446
9823
  return {
8447
9824
  dex,
@@ -8544,6 +9921,128 @@ async function _HyperLiquidProvider_excludeNonUsdcCollateralResults(results) {
8544
9921
  return false;
8545
9922
  }
8546
9923
  return Date.now() - cached.timestamp < cached.ttl;
9924
+ }, _HyperLiquidProvider_performDisconnect =
9925
+ /**
9926
+ * Perform one provider teardown shared by every concurrent caller.
9927
+ *
9928
+ * @returns A promise that resolves to the result.
9929
+ */
9930
+ async function _HyperLiquidProvider_performDisconnect() {
9931
+ __classPrivateFieldSet(this, _HyperLiquidProvider_chasePlacementBlockers, __classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") + 1, "f");
9932
+ __classPrivateFieldSet(this, _HyperLiquidProvider_strategyGeneration, __classPrivateFieldGet(this, _HyperLiquidProvider_strategyGeneration, "f") + 1, "f");
9933
+ __classPrivateFieldSet(this, _HyperLiquidProvider_disconnectOperationsInFlight, __classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") + 1, "f");
9934
+ __classPrivateFieldSet(this, _HyperLiquidProvider_isDisconnected, true, "f");
9935
+ __classPrivateFieldSet(this, _HyperLiquidProvider_lifecycleGeneration, __classPrivateFieldGet(this, _HyperLiquidProvider_lifecycleGeneration, "f") + 1, "f");
9936
+ try {
9937
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Disconnecting provider', {
9938
+ isTestnet: __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").isTestnetMode(),
9939
+ timestamp: new Date().toISOString(),
9940
+ });
9941
+ // Clear subscriptions through subscription service
9942
+ __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionService, "f").clearAll();
9943
+ // Stop every chase loop: each holds a pending timer, and re-pricing after
9944
+ // a disconnect would sign orders against a client that is being torn down.
9945
+ // Orders already resting are deliberately left alone — disconnecting is
9946
+ // not a request to cancel the user's positions or orders.
9947
+ // Invalidate placements first, then drain every mutation before clearing
9948
+ // the registry and tearing down the clients they use.
9949
+ for (const [sessionId, session] of __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").entries()) {
9950
+ if (session.active) {
9951
+ __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_stopChaseSession).call(this, sessionId);
9952
+ }
9953
+ }
9954
+ await Promise.all([...__classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementWaiters, "f")]);
9955
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTickQueue, "f");
9956
+ await Promise.allSettled([...__classPrivateFieldGet(this, _HyperLiquidProvider_chaseTerminations, "f").values()]);
9957
+ await Promise.all([...__classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").values()].map(async (session) => await __classPrivateFieldGet(this, _HyperLiquidProvider_instances, "m", _HyperLiquidProvider_rebalanceChaseSession).call(this, session)));
9958
+ __classPrivateFieldGet(this, _HyperLiquidProvider_chaseSessions, "f").clear();
9959
+ __classPrivateFieldGet(this, _HyperLiquidProvider_chaseTerminations, "f").clear();
9960
+ __classPrivateFieldGet(this, _HyperLiquidProvider_terminatedChaseHandles, "f").clear();
9961
+ __classPrivateFieldGet(this, _HyperLiquidProvider_scaleOrderGroups, "f").clear();
9962
+ __classPrivateFieldGet(this, _HyperLiquidProvider_cancelledScaleOrderGroups, "f").clear();
9963
+ // Clear session caches. Capability and fee reads use the lifecycle
9964
+ // generation above to discard any old response that resolves later.
9965
+ this.clearFeeCache();
9966
+ __classPrivateFieldGet(this, _HyperLiquidProvider_referralCheckCache, "f").clear();
9967
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeCheckCache, "f").clear();
9968
+ __classPrivateFieldSet(this, _HyperLiquidProvider_subscriptionBuilderApprovalEpoch, __classPrivateFieldGet(this, _HyperLiquidProvider_subscriptionBuilderApprovalEpoch, "f") + 1, "f");
9969
+ __classPrivateFieldGet(this, _HyperLiquidProvider_approvedBuilderAddresses, "f").clear();
9970
+ __classPrivateFieldSet(this, _HyperLiquidProvider_userFeeResolution, undefined, "f");
9971
+ __classPrivateFieldSet(this, _HyperLiquidProvider_userFeeDiscountBips, undefined, "f");
9972
+ // UnifiedAccountCache stays global to avoid repeated signing requests.
9973
+ __classPrivateFieldGet(this, _HyperLiquidProvider_cachedMetaByDex, "f").clear();
9974
+ __classPrivateFieldSet(this, _HyperLiquidProvider_pendingValidatedDexsPromise, null, "f");
9975
+ __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesMarketsByDex, "f").clear();
9976
+ __classPrivateFieldGet(this, _HyperLiquidProvider_orderCapabilitiesRefreshByDex, "f").clear();
9977
+ __classPrivateFieldSet(this, _HyperLiquidProvider_cachedSpotMeta, null, "f");
9978
+ __classPrivateFieldGet(this, _HyperLiquidProvider_dexDiscoveryCache, "f").reset();
9979
+ __classPrivateFieldSet(this, _HyperLiquidProvider_dexDiscoveryComplete, false, "f");
9980
+ // Await pending initialization before clearing to prevent the IIFE from
9981
+ // setting clientsInitialized = true after disconnect completes
9982
+ const pendingInit = __classPrivateFieldGet(this, _HyperLiquidProvider_initializationPromise, "f");
9983
+ const pendingReady = __classPrivateFieldGet(this, _HyperLiquidProvider_ensureReadyPromise, "f");
9984
+ const pendingTradingSetup = __classPrivateFieldGet(this, _HyperLiquidProvider_tradingSetupPromise, "f");
9985
+ const pendingBuilderFeeSetups = [
9986
+ ...__classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").values(),
9987
+ ];
9988
+ // Clear references first to prevent new callers from reusing
9989
+ __classPrivateFieldSet(this, _HyperLiquidProvider_initializationPromise, null, "f");
9990
+ __classPrivateFieldSet(this, _HyperLiquidProvider_ensureReadyPromise, null, "f");
9991
+ __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupPromise, null, "f");
9992
+ __classPrivateFieldSet(this, _HyperLiquidProvider_tradingSetupComplete, false, "f");
9993
+ __classPrivateFieldGet(this, _HyperLiquidProvider_builderFeeSetupPromises, "f").clear();
9994
+ __classPrivateFieldGet(this, _HyperLiquidProvider_pendingBuilderFeeApprovals, "f").clear();
9995
+ // Wait for pending operations to complete (ignore errors)
9996
+ // This prevents IIFEs from setting state after disconnect completes
9997
+ if (pendingInit) {
9998
+ try {
9999
+ await pendingInit;
10000
+ }
10001
+ catch {
10002
+ // Ignore - we're disconnecting anyway
10003
+ }
10004
+ }
10005
+ if (pendingReady) {
10006
+ try {
10007
+ await pendingReady;
10008
+ }
10009
+ catch {
10010
+ // Ignore - we're disconnecting anyway
10011
+ }
10012
+ }
10013
+ if (pendingTradingSetup) {
10014
+ try {
10015
+ await pendingTradingSetup;
10016
+ }
10017
+ catch {
10018
+ // Ignore - we're disconnecting anyway
10019
+ }
10020
+ }
10021
+ for (const pendingBuilderFeeSetup of pendingBuilderFeeSetups) {
10022
+ try {
10023
+ await pendingBuilderFeeSetup;
10024
+ }
10025
+ catch {
10026
+ // Ignore - we're disconnecting anyway
10027
+ }
10028
+ }
10029
+ // Reset client initialization flag so wallet adapter will be recreated with new account
10030
+ // This fixes account synchronization issue where old account's address persists in wallet adapter
10031
+ __classPrivateFieldSet(this, _HyperLiquidProvider_clientsInitialized, false, "f");
10032
+ // Disconnect client service
10033
+ await __classPrivateFieldGet(this, _HyperLiquidProvider_clientService, "f").disconnect();
10034
+ __classPrivateFieldGet(this, _HyperLiquidProvider_deps, "f").debugLogger.log('HyperLiquid: Provider fully disconnected', {
10035
+ timestamp: new Date().toISOString(),
10036
+ });
10037
+ return { success: true };
10038
+ }
10039
+ catch (error) {
10040
+ return (0, hyperLiquidValidation_js_1.createErrorResult)(error, { success: false });
10041
+ }
10042
+ finally {
10043
+ __classPrivateFieldSet(this, _HyperLiquidProvider_chasePlacementBlockers, __classPrivateFieldGet(this, _HyperLiquidProvider_chasePlacementBlockers, "f") - 1, "f");
10044
+ __classPrivateFieldSet(this, _HyperLiquidProvider_disconnectOperationsInFlight, __classPrivateFieldGet(this, _HyperLiquidProvider_disconnectOperationsInFlight, "f") - 1, "f");
10045
+ }
8547
10046
  }, _HyperLiquidProvider_getBuilderAddress = function _HyperLiquidProvider_getBuilderAddress(isTestnet) {
8548
10047
  // || intentional: env vars default to '' which must fall through to the hardcoded default
8549
10048
  if (isTestnet) {
@@ -8797,4 +10296,9 @@ async function _HyperLiquidProvider_setReferralCode() {
8797
10296
  throw error;
8798
10297
  }
8799
10298
  };
10299
+ // Native TWAP IDs and any manual HIP-3 collateral transfer must outlive one
10300
+ // provider instance: controller disconnect/reinitialization replaces the
10301
+ // provider while the venue schedule keeps running. Each controller messenger
10302
+ // owns one registry, so separate controller lifecycles never share handles.
10303
+ _HyperLiquidProvider_trackedTwapOrdersByMessenger = { value: new WeakMap() };
8800
10304
  //# sourceMappingURL=HyperLiquidProvider.cjs.map