@metamask-previews/perps-controller 22.0.0-preview-896e0b6 → 22.0.0-preview-406090321

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.
@@ -791,9 +791,6 @@ export class HyperLiquidProvider {
791
791
  // Session cache for referral state (cleared on disconnect/reconnect)
792
792
  // Key: `network:userAddress`, Value: true if referral is set
793
793
  #referralCheckCache = new Map();
794
- // Session-scoped, account/network-keyed support probes. Provider instances
795
- // are discarded on reconnect; rejected probes are not retained.
796
- #accountSupportByContext = new Map();
797
794
  // Session cache for builder fee approval state (cleared on disconnect/reconnect)
798
795
  // Key: `network:userAddress`, Value: true if builder fee is approved
799
796
  #builderFeeCheckCache = new Map();
@@ -1739,21 +1736,19 @@ export class HyperLiquidProvider {
1739
1736
  * multi-sig account with `ApiRequestError: Multi-sig required`, so the
1740
1737
  * unified-account migration must not be attempted for those accounts.
1741
1738
  *
1742
- * If the probe throws (transient network), returns `undefined` so callers
1743
- * can fail open without caching the transient result.
1739
+ * If the probe throws (transient network), returns `false` — fail open so
1740
+ * one bad probe never blocks migration for a normal single-signer account.
1744
1741
  * The `isHyperLiquidMultiSigRequiredError` fallback in the write's catch
1745
1742
  * block remains the safety net.
1746
1743
  *
1747
1744
  * @param userAddress - The wallet address to check.
1748
1745
  * @param infoClient - Optional captured reader for an operation-scoped probe.
1749
- * @returns True for multi-sig, false for single-signer, or undefined when
1750
- * the probe could not complete.
1746
+ * @returns True only when Hyperliquid reports a multi-sig signer set.
1751
1747
  * @private
1752
1748
  */
1753
- async #isHyperliquidMultiSigAccount(userAddress, infoClient) {
1749
+ async #isHyperliquidMultiSigAccount(userAddress, infoClient = this.#clientService.getInfoClient()) {
1754
1750
  try {
1755
- const reader = infoClient ?? this.#clientService.getInfoClient();
1756
- const signers = await reader.userToMultiSigSigners({
1751
+ const signers = await infoClient.userToMultiSigSigners({
1757
1752
  user: userAddress,
1758
1753
  });
1759
1754
  return signers !== null && signers !== undefined;
@@ -1763,165 +1758,8 @@ export class HyperLiquidProvider {
1763
1758
  user: userAddress,
1764
1759
  error: ensureError(error, 'HyperLiquidProvider.isHyperliquidMultiSigAccount').message,
1765
1760
  });
1766
- return undefined;
1767
- }
1768
- }
1769
- #getAccountSupportCacheKey(network, userAddress) {
1770
- return `${network}:${userAddress.toLowerCase()}`;
1771
- }
1772
- #recordAccountSupport(network, userAddress, support) {
1773
- this.#accountSupportByContext.set(this.#getAccountSupportCacheKey(network, userAddress), Promise.resolve(support));
1774
- }
1775
- /**
1776
- * Replace an optimistic support result after the venue authoritatively
1777
- * rejects a write from the account that supplied its signature.
1778
- *
1779
- * The captured context, rather than the currently selected account, owns the
1780
- * rejection because selection may change while the request is in flight.
1781
- *
1782
- * @param error - The failed write or mapped account-mode error.
1783
- * @param accountContext - Account and network that authorized the write.
1784
- * @returns Whether the rejection requires Hyperliquid multi-signature.
1785
- */
1786
- #recordUnsupportedAccountFromError(error, accountContext) {
1787
- const isMultiSigRequired = isHyperLiquidMultiSigRequiredError(error) ||
1788
- ensureError(error, 'HyperLiquidProvider.recordUnsupportedAccountFromError').message === PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED;
1789
- if (!isMultiSigRequired) {
1790
1761
  return false;
1791
1762
  }
1792
- this.#recordAccountSupport(accountContext.network, accountContext.userAddress, {
1793
- isSupported: false,
1794
- reason: 'multi_sig_account',
1795
- });
1796
- return true;
1797
- }
1798
- /**
1799
- * Correct support from errors returned as resolved per-entry statuses.
1800
- *
1801
- * @param statuses - Venue status entries.
1802
- * @param answer - Full venue answer that owns the statuses.
1803
- * @param accountContext - Account and network that authorized the write.
1804
- * @returns Whether any status requires Hyperliquid multi-signature.
1805
- */
1806
- #recordUnsupportedAccountFromStatuses(statuses, answer, accountContext) {
1807
- return statuses.some((status) => isStatusObject(status) &&
1808
- typeof status.error === 'string' &&
1809
- this.#recordUnsupportedAccountFromError(createStatusError(status.error, answer), accountContext));
1810
- }
1811
- /**
1812
- * Read support for an account context without initializing the provider.
1813
- * Successful results are stable for the provider session and coalesced by
1814
- * account and network. Failed probes fail open and remain retryable.
1815
- *
1816
- * @param context - An operation-scoped account context. Defaults to the
1817
- * currently active account and network.
1818
- * @returns Whether the account can submit Hyperliquid actions.
1819
- */
1820
- async #getAccountSupportForContext(context) {
1821
- const userAddress = context?.userAddress ??
1822
- (await this.#walletService.getUserAddressWithDefault());
1823
- const network = context?.network ??
1824
- (this.#clientService.isTestnetMode() ? 'testnet' : 'mainnet');
1825
- const cacheKey = this.#getAccountSupportCacheKey(network, userAddress);
1826
- const cachedSupport = this.#accountSupportByContext.get(cacheKey);
1827
- if (cachedSupport) {
1828
- return cachedSupport;
1829
- }
1830
- const probe = this.#isHyperliquidMultiSigAccount(userAddress, context?.infoClient);
1831
- const support = probe.then((isMultiSig) => isMultiSig
1832
- ? { isSupported: false, reason: 'multi_sig_account' }
1833
- : { isSupported: true }, () => ({ isSupported: true }));
1834
- this.#accountSupportByContext.set(cacheKey, support);
1835
- probe
1836
- .then((isMultiSig) => {
1837
- if (isMultiSig === undefined &&
1838
- this.#accountSupportByContext.get(cacheKey) === support) {
1839
- this.#accountSupportByContext.delete(cacheKey);
1840
- }
1841
- }, () => {
1842
- if (this.#accountSupportByContext.get(cacheKey) === support) {
1843
- this.#accountSupportByContext.delete(cacheKey);
1844
- }
1845
- })
1846
- .catch(() => undefined);
1847
- return support;
1848
- }
1849
- async #assertAccountContextCurrent(context, operation) {
1850
- this.#assertProviderLifecycleCurrent(context.lifecycleGeneration, operation);
1851
- await this.#assertAccountIdentityCurrent(context);
1852
- this.#assertProviderLifecycleCurrent(context.lifecycleGeneration, operation);
1853
- }
1854
- /**
1855
- * Confirm that the selected address and network still match an action,
1856
- * without requiring the provider lifecycle that created it to remain active.
1857
- *
1858
- * @param context - The account and network captured by the action.
1859
- */
1860
- async #assertAccountIdentityCurrent(context) {
1861
- const currentAddress = await this.#walletService
1862
- .getUserAddressWithDefault()
1863
- .catch(() => undefined);
1864
- const currentNetwork = this.#clientService.isTestnetMode()
1865
- ? 'testnet'
1866
- : 'mainnet';
1867
- if (currentAddress?.toLowerCase() !== context.userAddress.toLowerCase() ||
1868
- currentNetwork !== context.network) {
1869
- throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
1870
- }
1871
- }
1872
- /**
1873
- * Permit best-effort rollback after teardown only while it still belongs to
1874
- * the same address and network, and the account is not known unsupported.
1875
- *
1876
- * @param context - The account and network captured by the parent action.
1877
- */
1878
- async #assertRollbackAccountWritable(context) {
1879
- await this.#assertAccountIdentityCurrent(context);
1880
- const cachedSupport = this.#accountSupportByContext.get(this.#getAccountSupportCacheKey(context.network, context.userAddress));
1881
- const support = cachedSupport ? await cachedSupport : undefined;
1882
- await this.#assertAccountIdentityCurrent(context);
1883
- if (support && !support.isSupported) {
1884
- throw new Error(PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED);
1885
- }
1886
- }
1887
- /**
1888
- * Block an action when another step has authoritatively corrected support
1889
- * for its captured account. A missing cache entry remains fail-open: this
1890
- * fence must not turn a transient probe failure into another network probe.
1891
- *
1892
- * @param context - The account, network, and lifecycle captured by the action.
1893
- * @param operation - Operation name included in lifecycle diagnostics.
1894
- */
1895
- async #assertNoKnownUnsupportedAccount(context, operation) {
1896
- await this.#assertAccountContextCurrent(context, operation);
1897
- const cachedSupport = this.#accountSupportByContext.get(this.#getAccountSupportCacheKey(context.network, context.userAddress));
1898
- const support = cachedSupport ? await cachedSupport : undefined;
1899
- await this.#assertAccountContextCurrent(context, operation);
1900
- if (support && !support.isSupported) {
1901
- throw new Error(PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED);
1902
- }
1903
- }
1904
- /**
1905
- * Stop an action before signing when Hyperliquid reports a native multi-sig
1906
- * signer set for the active account.
1907
- *
1908
- * @param expectedContext - Existing operation context to validate, or omit
1909
- * to capture the currently selected account and network.
1910
- * @returns The account context whose support was checked.
1911
- */
1912
- async #assertAccountSupported(expectedContext) {
1913
- const context = expectedContext ??
1914
- {
1915
- lifecycleGeneration: this.#lifecycleGeneration,
1916
- network: this.#clientService.isTestnetMode() ? 'testnet' : 'mainnet',
1917
- userAddress: await this.#walletService.getUserAddressWithDefault(),
1918
- };
1919
- const support = await this.#getAccountSupportForContext(context);
1920
- await this.#assertAccountContextCurrent(context, 'Account support check');
1921
- if (!support.isSupported) {
1922
- throw new Error(PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED);
1923
- }
1924
- return context;
1925
1763
  }
1926
1764
  /**
1927
1765
  * Attempt to enable HyperLiquid Unified Account mode for HIP-3 orders
@@ -1934,8 +1772,6 @@ export class HyperLiquidProvider {
1934
1772
  *
1935
1773
  * @param options - Optional configuration.
1936
1774
  * @param options.allowUserSigning - When true, runs the migration for `default` / `disabled` accounts. Defaults to false so init does not surface a signing prompt; action-time entry points (trading, withdraw) pass true.
1937
- * @param options.accountContext - Account/network/lifecycle captured by the
1938
- * action that is allowed to authorize the migration.
1939
1775
  * @private
1940
1776
  */
1941
1777
  async #ensureUnifiedAccountEnabled(options) {
@@ -1954,11 +1790,8 @@ export class HyperLiquidProvider {
1954
1790
  if (!this.#useUnifiedAccount) {
1955
1791
  return; // Feature disabled
1956
1792
  }
1957
- const { accountContext } = options ?? {};
1958
- const userAddress = accountContext?.userAddress ??
1959
- (await this.#walletService.getUserAddressWithDefault());
1960
- const network = accountContext?.network ??
1961
- (this.#clientService.isTestnetMode() ? 'testnet' : 'mainnet');
1793
+ const userAddress = await this.#walletService.getUserAddressWithDefault();
1794
+ const network = this.#clientService.isTestnetMode() ? 'testnet' : 'mainnet';
1962
1795
  // Check global cache first to avoid repeated signing requests
1963
1796
  // This is CRITICAL for hardware wallets to prevent repeated signing prompts
1964
1797
  // while browsing.
@@ -2026,9 +1859,6 @@ export class HyperLiquidProvider {
2026
1859
  currentMode = await infoClient.userAbstraction({
2027
1860
  user: userAddress,
2028
1861
  });
2029
- if (accountContext) {
2030
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Unified account mode lookup');
2031
- }
2032
1862
  if (currentMode === 'unifiedAccount' ||
2033
1863
  currentMode === 'portfolioMargin') {
2034
1864
  // portfolioMargin is a superset of unifiedAccount — it already supports
@@ -2072,21 +1902,17 @@ export class HyperLiquidProvider {
2072
1902
  // surfaced on the Perps tab on every entry (TAT-3214). Probe right
2073
1903
  // before the write so accounts that never reach one (already compatible,
2074
1904
  // deferred, unknown mode) do not pay the extra round trip.
2075
- const accountSupport = await this.#getAccountSupportForContext({
2076
- userAddress,
2077
- network,
2078
- infoClient,
2079
- });
2080
- if (!accountSupport.isSupported) {
1905
+ const isMultiSig = await this.#isHyperliquidMultiSigAccount(userAddress);
1906
+ if (isMultiSig) {
2081
1907
  this.#deps.debugLogger.log('[ensureUnifiedAccountEnabled] Multi-sig account, skipping unified account migration', { user: userAddress, network, mode: currentMode });
2082
1908
  this.#deps.metrics.trackPerpsEvent(PerpsAnalyticsEvent.AccountSetup, {
2083
1909
  [PERPS_EVENT_PROPERTY.PREVIOUS_ABSTRACTION_MODE]: currentMode,
2084
1910
  [PERPS_EVENT_PROPERTY.STATUS]: PERPS_EVENT_VALUE.STATUS.NOT_APPLICABLE,
2085
1911
  [PERPS_EVENT_PROPERTY.ERROR_MESSAGE]: 'multi_sig_account',
2086
1912
  });
2087
- // Final state: the migration can never succeed for this account, so
2088
- // cache it as attempted with unified mode off. Action readiness blocks
2089
- // this account before any later exchange write.
1913
+ // Final state: the write can never succeed for this account, so cache
1914
+ // it as attempted with unified mode off. Perps keeps working through
1915
+ // the programmatic collateral-transfer fallback.
2090
1916
  TradingReadinessCache.set(network, userAddress, {
2091
1917
  attempted: true,
2092
1918
  enabled: false,
@@ -2107,13 +1933,7 @@ export class HyperLiquidProvider {
2107
1933
  previousMode: currentMode,
2108
1934
  note: 'HyperLiquid will auto-manage collateral for HIP-3 orders',
2109
1935
  });
2110
- if (accountContext) {
2111
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Unified account migration');
2112
- }
2113
- const exchangeClient = this.#clientService.getExchangeClient(accountContext
2114
- ? async () => await this.#assertNoKnownUnsupportedAccount(accountContext, 'Unified account migration dispatch')
2115
- : undefined);
2116
- await exchangeClient.agentSetAbstraction({
1936
+ await this.#clientService.getExchangeClient().agentSetAbstraction({
2117
1937
  abstraction: HL_ABSTRACTION_WIRE.unifiedAccount,
2118
1938
  });
2119
1939
  this.#deps.debugLogger.log('✅ HyperLiquidProvider: Unified Account enabled successfully');
@@ -2168,10 +1988,6 @@ export class HyperLiquidProvider {
2168
1988
  // account-shape condition, not a failure worth reporting or retrying.
2169
1989
  if (isHyperLiquidMultiSigRequiredError(error)) {
2170
1990
  this.#deps.debugLogger.log('[ensureUnifiedAccountEnabled] Multi-sig account (race/probe fallback), skipping unified account migration', { user: userAddress, network, mode: currentMode });
2171
- this.#recordAccountSupport(network, userAddress, {
2172
- isSupported: false,
2173
- reason: 'multi_sig_account',
2174
- });
2175
1991
  this.#deps.metrics.trackPerpsEvent(PerpsAnalyticsEvent.AccountSetup, {
2176
1992
  ...(currentMode && {
2177
1993
  [PERPS_EVENT_PROPERTY.PREVIOUS_ABSTRACTION_MODE]: currentMode,
@@ -2204,12 +2020,6 @@ export class HyperLiquidProvider {
2204
2020
  completeInFlight();
2205
2021
  return;
2206
2022
  }
2207
- if (accountContext &&
2208
- ensureError(error, 'HyperLiquidProvider.ensureUnifiedAccountEnabled')
2209
- .message === PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE) {
2210
- completeInFlight();
2211
- throw error;
2212
- }
2213
2023
  // Agent-path failures and read-only userAbstraction lookup failures
2214
2024
  // are not final: signal #ensureReady to drop its memoized promise and
2215
2025
  // retry on the next entry instead of pinning the user in the
@@ -2278,26 +2088,10 @@ export class HyperLiquidProvider {
2278
2088
  // over. The memoized promise is dropped while the cooldown is active
2279
2089
  // (see below), so a later entry retries. Action-time callers are not
2280
2090
  // gated.
2281
- if (!this.#isDisconnected &&
2282
- Date.now() >= this.#unifiedAccountTransportRetryAt) {
2283
- if (this.#walletService.requiresSignatureConfirmation()) {
2284
- await this.#ensureUnifiedAccountEnabled({
2285
- allowUserSigning: false,
2286
- });
2287
- }
2288
- else {
2289
- const accountContext = {
2290
- lifecycleGeneration: this.#lifecycleGeneration,
2291
- network: this.#clientService.isTestnetMode()
2292
- ? 'testnet'
2293
- : 'mainnet',
2294
- userAddress: await this.#walletService.getUserAddressWithDefault(),
2295
- };
2296
- await this.#ensureUnifiedAccountEnabled({
2297
- allowUserSigning: true,
2298
- accountContext,
2299
- });
2300
- }
2091
+ if (Date.now() >= this.#unifiedAccountTransportRetryAt) {
2092
+ await this.#ensureUnifiedAccountEnabled({
2093
+ allowUserSigning: !this.#walletService.requiresSignatureConfirmation(),
2094
+ });
2301
2095
  }
2302
2096
  })();
2303
2097
  // Await initialization - keep the promise so subsequent calls resolve immediately
@@ -2375,17 +2169,13 @@ export class HyperLiquidProvider {
2375
2169
  * code (the approval is otherwise non-blocking).
2376
2170
  * @param options.reportRefusal - Throw the venue's refusal, even without an
2377
2171
  * approval failure code.
2378
- * @param accountContext - Account/network/lifecycle captured by the action
2379
- * that is allowed to authorize this approval.
2380
2172
  * @returns The account, network, and configured builder for the action.
2381
2173
  */
2382
- async #ensureBuilderFeeSetup(approvalFailureCode, options = {}, accountContext) {
2383
- const lifecycleGeneration = accountContext?.lifecycleGeneration ?? this.#lifecycleGeneration;
2384
- const network = accountContext?.network ??
2385
- (this.#clientService.isTestnetMode() ? 'testnet' : 'mainnet');
2386
- const isTestnet = network === 'testnet';
2387
- const userAddress = accountContext?.userAddress ??
2388
- (await this.#walletService.getUserAddressWithDefault());
2174
+ async #ensureBuilderFeeSetup(approvalFailureCode, options = {}) {
2175
+ const lifecycleGeneration = this.#lifecycleGeneration;
2176
+ const isTestnet = this.#clientService.isTestnetMode();
2177
+ const network = isTestnet ? 'testnet' : 'mainnet';
2178
+ const userAddress = await this.#walletService.getUserAddressWithDefault();
2389
2179
  const cacheKey = this.#getCacheKey(network, userAddress);
2390
2180
  const builderAddress = this.#getBuilderAddress(isTestnet);
2391
2181
  const context = {
@@ -2413,7 +2203,7 @@ export class HyperLiquidProvider {
2413
2203
  }
2414
2204
  let pendingApproval = this.#builderFeeSetupPromises.get(setupKey);
2415
2205
  if (!pendingApproval) {
2416
- pendingApproval = this.#ensureBuilderFeeApproval(context, accountContext);
2206
+ pendingApproval = this.#ensureBuilderFeeApproval(context);
2417
2207
  this.#builderFeeSetupPromises.set(setupKey, pendingApproval);
2418
2208
  }
2419
2209
  try {
@@ -2421,10 +2211,6 @@ export class HyperLiquidProvider {
2421
2211
  }
2422
2212
  catch (error) {
2423
2213
  this.#deps.debugLogger.log('[ensureBuilderFeeSetup] Builder fee approval failed', error);
2424
- if (accountContext &&
2425
- this.#recordUnsupportedAccountFromError(error, accountContext)) {
2426
- throw new Error(PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED);
2427
- }
2428
2214
  // A signer that could not sign is retryable, not an approval failure.
2429
2215
  if (approvalFailureCode || options.reportSignerFailure) {
2430
2216
  const signerFailure = await this.#classifySignerFailure(error);
@@ -2457,15 +2243,10 @@ export class HyperLiquidProvider {
2457
2243
  async #ensureReadyForTrading(options) {
2458
2244
  // First ensure basic initialization is complete
2459
2245
  await this.#ensureReady();
2460
- const accountContext = await this.#assertAccountSupported(options.accountContext);
2461
2246
  // The migration was deferred during init to avoid a signing prompt on
2462
2247
  // Perps section open. Drive it here, gated by its own cache so
2463
2248
  // already-migrated users are not re-prompted.
2464
- await this.#ensureUnifiedAccountEnabled({
2465
- allowUserSigning: true,
2466
- accountContext,
2467
- });
2468
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Trading setup');
2249
+ await this.#ensureUnifiedAccountEnabled({ allowUserSigning: true });
2469
2250
  // Reset right before the check, with no await in between, so a failure
2470
2251
  // above does not leave the setup for an order to run.
2471
2252
  if (options.recheckPendingReferral && this.#referralAwaitsBuilderCode) {
@@ -2487,7 +2268,7 @@ export class HyperLiquidProvider {
2487
2268
  }
2488
2269
  }
2489
2270
  // Set up referral code independently from builder-fee applicability.
2490
- await this.#ensureReferralSet(accountContext);
2271
+ await this.#ensureReferralSet();
2491
2272
  this.#assertProviderLifecycleCurrent(lifecycleGeneration, 'Trading setup completion');
2492
2273
  // Only mark complete if the main-account signer is ready (signing
2493
2274
  // could actually happen) and the referral does not need another
@@ -2510,14 +2291,10 @@ export class HyperLiquidProvider {
2510
2291
  }
2511
2292
  }
2512
2293
  const builderFeeSetupContext = options.requiresBuilderFee
2513
- ? await this.#ensureBuilderFeeSetup(options.builderFeeApprovalFailureCode, {}, accountContext)
2294
+ ? await this.#ensureBuilderFeeSetup(options.builderFeeApprovalFailureCode)
2514
2295
  : undefined;
2515
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Trading setup');
2516
2296
  this.#deps.debugLogger.log('[ensureReadyForTrading] Trading setup complete');
2517
- return {
2518
- accountContext,
2519
- ...(builderFeeSetupContext && { builderFeeSetupContext }),
2520
- };
2297
+ return builderFeeSetupContext;
2521
2298
  }
2522
2299
  /**
2523
2300
  * Get current price for a symbol using WebSocket cache first, REST API fallback
@@ -3451,14 +3228,10 @@ export class HyperLiquidProvider {
3451
3228
  *
3452
3229
  * @param message - The status entry's error.
3453
3230
  * @param answer - The venue answer with the entry.
3454
- * @param accountContext - Account and network that authorized the write.
3455
3231
  * @returns The mapped error.
3456
3232
  */
3457
- async #mapStatusError(message, answer, accountContext) {
3233
+ async #mapStatusError(message, answer) {
3458
3234
  const error = createStatusError(message, answer);
3459
- if (accountContext) {
3460
- this.#recordUnsupportedAccountFromError(error, accountContext);
3461
- }
3462
3235
  return (await this.#classifySignerFailure(error)) ?? this.#mapError(error);
3463
3236
  }
3464
3237
  /**
@@ -3587,17 +3360,10 @@ export class HyperLiquidProvider {
3587
3360
  * @param params.network - HyperLiquid network for the approval.
3588
3361
  * @param params.userAddress - Account that owns the approval.
3589
3362
  * @param params.builderAddress - Builder address being approved.
3590
- * @param accountContext - Account/network/lifecycle captured by the action
3591
- * that is allowed to authorize this approval.
3592
3363
  */
3593
- async #ensureBuilderFeeApproval(params, accountContext) {
3594
- const lifecycleGeneration = accountContext?.lifecycleGeneration ?? this.#lifecycleGeneration;
3364
+ async #ensureBuilderFeeApproval(params) {
3365
+ const lifecycleGeneration = this.#lifecycleGeneration;
3595
3366
  const { network, userAddress, builderAddress } = params;
3596
- const supportContext = accountContext ?? {
3597
- lifecycleGeneration,
3598
- network,
3599
- userAddress,
3600
- };
3601
3367
  this.#assertProviderLifecycleCurrent(lifecycleGeneration, 'Builder fee approval');
3602
3368
  const cacheKey = this.#getCacheKey(network, userAddress);
3603
3369
  // Check GLOBAL cache first to avoid repeated signing requests across reconnections
@@ -3635,12 +3401,7 @@ export class HyperLiquidProvider {
3635
3401
  return;
3636
3402
  }
3637
3403
  const { isApproved, requiredDecimal } = await this.#checkBuilderFeeStatus(builderAddress, userAddress);
3638
- if (accountContext) {
3639
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Builder fee approval lookup');
3640
- }
3641
- else {
3642
- this.#assertProviderLifecycleCurrent(lifecycleGeneration, 'Builder fee approval');
3643
- }
3404
+ this.#assertProviderLifecycleCurrent(lifecycleGeneration, 'Builder fee approval');
3644
3405
  if (isApproved) {
3645
3406
  // User already has approval on-chain
3646
3407
  PerpsSigningCache.setBuilderFee(network, userAddress, {
@@ -3653,20 +3414,12 @@ export class HyperLiquidProvider {
3653
3414
  }
3654
3415
  else {
3655
3416
  this.#deps.debugLogger.log('[ensureBuilderFeeApproval] Approval required (will show signing request)', { builder: builderAddress, requiredDecimal });
3656
- if (accountContext) {
3657
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Builder fee approval');
3658
- }
3659
3417
  const exchangeClient = this.#clientService.getExchangeClient();
3660
3418
  const maxFeeRate = BUILDER_FEE_CONFIG.MaxFeeRate;
3661
- const approvalResult = await exchangeClient.approveBuilderFee({
3419
+ await exchangeClient.approveBuilderFee({
3662
3420
  builder: builderAddress,
3663
3421
  maxFeeRate,
3664
3422
  });
3665
- if (approvalResult.status !== 'ok') {
3666
- const approvalError = new Error(`Builder fee approval failed: ${JSON.stringify(approvalResult)}`);
3667
- this.#recordUnsupportedAccountFromError(approvalError, supportContext);
3668
- throw approvalError;
3669
- }
3670
3423
  // Verify approval was successful before caching
3671
3424
  const afterApprovalDecimal = await this.#checkBuilderFeeApproval(builderAddress, userAddress);
3672
3425
  if (afterApprovalDecimal === null ||
@@ -3692,7 +3445,6 @@ export class HyperLiquidProvider {
3692
3445
  completeInFlight();
3693
3446
  }
3694
3447
  catch (error) {
3695
- this.#recordUnsupportedAccountFromError(error, supportContext);
3696
3448
  if (ensureError(error, 'HyperLiquidProvider.ensureBuilderFeeApproval')
3697
3449
  .message === PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE) {
3698
3450
  completeInFlight();
@@ -3889,13 +3641,11 @@ export class HyperLiquidProvider {
3889
3641
  * @param params - Transfer parameters
3890
3642
  * @param params.targetDex - HIP-3 DEX name (e.g., 'xyz')
3891
3643
  * @param params.requiredMargin - Required margin with buffer
3892
- * @param params.accountContext - Account/network/lifecycle captured by the
3893
- * order that may authorize a transfer.
3894
3644
  * @returns Transfer info for rollback, or null if no transfer needed
3895
3645
  * @private
3896
3646
  */
3897
3647
  async #autoTransferForHip3Order(params) {
3898
- const { targetDex, requiredMargin, accountContext } = params;
3648
+ const { targetDex, requiredMargin } = params;
3899
3649
  // Check target DEX balance
3900
3650
  const targetBalance = await this.#getBalanceForDex({ dex: targetDex });
3901
3651
  this.#deps.debugLogger.log('HyperLiquidProvider: HIP-3 balance check', {
@@ -3924,19 +3674,15 @@ export class HyperLiquidProvider {
3924
3674
  to: targetDex,
3925
3675
  amount: transferAmount,
3926
3676
  });
3927
- const transferParams = {
3677
+ const result = await this.transferBetweenDexs({
3928
3678
  sourceDex: source.sourceDex,
3929
3679
  destinationDex: targetDex,
3930
3680
  amount: transferAmount,
3931
- };
3932
- const result = accountContext
3933
- ? await this.#transferBetweenDexsForContext(transferParams, accountContext)
3934
- : await this.transferBetweenDexs(transferParams);
3681
+ });
3935
3682
  if (!result.success) {
3936
- // Preserve stable account/signer outcomes that callers handle directly.
3937
- if (result.error === PERPS_ERROR_CODES.KEYRING_LOCKED ||
3938
- result.error === PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED) {
3939
- throw new Error(result.error);
3683
+ // The signer could not sign the transfer: retryable, not a defect.
3684
+ if (result.error === PERPS_ERROR_CODES.KEYRING_LOCKED) {
3685
+ throw new Error(PERPS_ERROR_CODES.KEYRING_LOCKED);
3940
3686
  }
3941
3687
  throw new Error(`Auto-transfer failed: ${result.error ?? 'Unknown error'}`);
3942
3688
  }
@@ -3964,13 +3710,11 @@ export class HyperLiquidProvider {
3964
3710
  * @param params.freedMargin - Amount of margin released from position close
3965
3711
  * @param params.transferAll - (Future) Transfer all available balance instead
3966
3712
  * @param params.skipTransfer - (Future) Skip auto-transfer if disabled
3967
- * @param params.accountContext - Original action context to preserve across
3968
- * the balance read and transfer.
3969
3713
  * @returns Transfer info if successful, null if skipped/failed
3970
3714
  * @private
3971
3715
  */
3972
3716
  async #autoTransferBackAfterClose(params) {
3973
- const { sourceDex, freedMargin, transferAll = false, skipTransfer = false, accountContext, } = params;
3717
+ const { sourceDex, freedMargin, transferAll = false, skipTransfer = false, } = params;
3974
3718
  // Future: Check user preference to skip auto-transfer
3975
3719
  if (skipTransfer) {
3976
3720
  this.#deps.debugLogger.log('Auto-transfer back skipped (disabled by config)');
@@ -4006,14 +3750,11 @@ export class HyperLiquidProvider {
4006
3750
  to: 'main',
4007
3751
  });
4008
3752
  // Execute transfer back to main DEX (empty string '' represents main DEX)
4009
- const transferParams = {
3753
+ const result = await this.transferBetweenDexs({
4010
3754
  sourceDex,
4011
3755
  destinationDex: '',
4012
3756
  amount: transferAmount.toFixed(USDC_DECIMALS),
4013
- };
4014
- const result = accountContext
4015
- ? await this.#transferBetweenDexsForContext(transferParams, accountContext)
4016
- : await this.transferBetweenDexs(transferParams);
3757
+ });
4017
3758
  if (!result.success) {
4018
3759
  this.#deps.debugLogger.log('❌ Auto-transfer back failed', {
4019
3760
  error: result.error,
@@ -4137,12 +3878,11 @@ export class HyperLiquidProvider {
4137
3878
  * @param params.transferInfo - The transfer information.
4138
3879
  * @param params.transferInfo.amount - The amount value.
4139
3880
  * @param params.transferInfo.sourceDex - The source DEX for the transfer.
4140
- * @param params.accountContext - Original action context for any rebalance.
4141
3881
  * @returns Whether the balance check and any required transfer succeeded.
4142
3882
  * @private
4143
3883
  */
4144
3884
  async #handleHip3PostOrderRebalance(params) {
4145
- const { accountContext, dexName, transferInfo } = params;
3885
+ const { dexName, transferInfo } = params;
4146
3886
  try {
4147
3887
  const postOrderBalance = await this.#getBalanceForDex({ dex: dexName });
4148
3888
  const transferredAmount = transferInfo.amount;
@@ -4167,11 +3907,11 @@ export class HyperLiquidProvider {
4167
3907
  excessAmount: excessAmount.toFixed(2),
4168
3908
  destinationDex: transferInfo.sourceDex,
4169
3909
  });
4170
- const transferResult = await this.#transferBetweenDexsForContext({
3910
+ const transferResult = await this.transferBetweenDexs({
4171
3911
  sourceDex: dexName,
4172
3912
  destinationDex: transferInfo.sourceDex,
4173
3913
  amount: excessAmount.toFixed(USDC_DECIMALS),
4174
- }, accountContext);
3914
+ });
4175
3915
  if (!transferResult.success) {
4176
3916
  // The signer could not sign the transfer: retryable, not a defect.
4177
3917
  if (transferResult.error === PERPS_ERROR_CODES.KEYRING_LOCKED) {
@@ -4231,11 +3971,10 @@ export class HyperLiquidProvider {
4231
3971
  * @param params.transferInfo - The transfer information.
4232
3972
  * @param params.transferInfo.amount - The amount value.
4233
3973
  * @param params.transferInfo.sourceDex - The source DEX for the transfer.
4234
- * @param params.accountContext - Original action context for the rollback.
4235
3974
  * @private
4236
3975
  */
4237
3976
  async #handleHip3OrderRollback(params) {
4238
- const { accountContext, dexName, transferInfo } = params;
3977
+ const { dexName, transferInfo } = params;
4239
3978
  try {
4240
3979
  this.#deps.debugLogger.log('HyperLiquidProvider: Rolling back failed order transfer', {
4241
3980
  from: dexName,
@@ -4243,11 +3982,11 @@ export class HyperLiquidProvider {
4243
3982
  amount: transferInfo.amount.toFixed(USDC_DECIMALS),
4244
3983
  reason: 'order_failed',
4245
3984
  });
4246
- const rollbackResult = await this.#transferBetweenDexsForContext({
3985
+ const rollbackResult = await this.transferBetweenDexs({
4247
3986
  sourceDex: dexName, // From HIP-3 DEX
4248
3987
  destinationDex: transferInfo.sourceDex, // Back to source
4249
3988
  amount: transferInfo.amount.toFixed(USDC_DECIMALS),
4250
- }, accountContext);
3989
+ });
4251
3990
  if (rollbackResult.success) {
4252
3991
  this.#deps.debugLogger.log('✅ HyperLiquidProvider: Rollback successful', {
4253
3992
  amount: transferInfo.amount.toFixed(USDC_DECIMALS),
@@ -4421,7 +4160,7 @@ export class HyperLiquidProvider {
4421
4160
  * @param params - The operation parameters.
4422
4161
  */
4423
4162
  async #prepareAssetForTrading(params) {
4424
- const { symbol, assetId, leverage, accountContext } = params;
4163
+ const { symbol, assetId, leverage } = params;
4425
4164
  if (!leverage) {
4426
4165
  return;
4427
4166
  }
@@ -4432,21 +4171,14 @@ export class HyperLiquidProvider {
4432
4171
  requestedLeverage: leverage,
4433
4172
  leverageType: marginMode,
4434
4173
  });
4435
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Leverage update');
4436
- try {
4437
- const exchangeClient = this.#clientService.getExchangeClient();
4438
- const leverageResult = await exchangeClient.updateLeverage({
4439
- asset: assetId,
4440
- isCross: marginMode === 'cross',
4441
- leverage,
4442
- });
4443
- if (leverageResult.status !== 'ok') {
4444
- throw new Error(`Failed to update leverage: ${JSON.stringify(leverageResult)}`);
4445
- }
4446
- }
4447
- catch (error) {
4448
- this.#recordUnsupportedAccountFromError(error, accountContext);
4449
- throw error;
4174
+ const exchangeClient = this.#clientService.getExchangeClient();
4175
+ const leverageResult = await exchangeClient.updateLeverage({
4176
+ asset: assetId,
4177
+ isCross: marginMode === 'cross',
4178
+ leverage,
4179
+ });
4180
+ if (leverageResult.status !== 'ok') {
4181
+ throw new Error(`Failed to update leverage: ${JSON.stringify(leverageResult)}`);
4450
4182
  }
4451
4183
  this.#deps.debugLogger.log('Leverage updated successfully:', {
4452
4184
  symbol,
@@ -4484,7 +4216,7 @@ export class HyperLiquidProvider {
4484
4216
  * @returns A promise that resolves to the result.
4485
4217
  */
4486
4218
  async #handleHip3PreOrder(params) {
4487
- const { accountContext, dexName, symbol, orderPrice, positionSize, leverage, isBuy, } = params;
4219
+ const { dexName, symbol, orderPrice, positionSize, leverage, isBuy } = params;
4488
4220
  // TAT-3304: Only USDC-collateral HIP-3 DEXs are supported for trading.
4489
4221
  // Following the USDH sunset, reject orders on any non-USDC-collateral
4490
4222
  // DEX here instead of attempting the (now-removed) USDH auto-swap path.
@@ -4522,7 +4254,6 @@ export class HyperLiquidProvider {
4522
4254
  const transferInfo = await this.#autoTransferForHip3Order({
4523
4255
  targetDex: dexName,
4524
4256
  requiredMargin: requiredMarginWithBuffer,
4525
- accountContext,
4526
4257
  });
4527
4258
  return { transferInfo };
4528
4259
  }
@@ -4547,7 +4278,7 @@ export class HyperLiquidProvider {
4547
4278
  const exchangeClient = this.#clientService.getExchangeClient();
4548
4279
  const builder = params.chargesMetamaskBuilderFee
4549
4280
  ? await this.#getBuilderOrderContext(params.builderFeeSetupContext ??
4550
- (await this.#ensureBuilderFeeSetup(undefined, {}, params.accountContext)))
4281
+ (await this.#ensureBuilderFeeSetup()))
4551
4282
  : undefined;
4552
4283
  this.#deps.debugLogger.log('Submitting order via asset ID routing', {
4553
4284
  symbol,
@@ -4558,7 +4289,6 @@ export class HyperLiquidProvider {
4558
4289
  isHip3: Boolean(dexName),
4559
4290
  });
4560
4291
  try {
4561
- await this.#assertNoKnownUnsupportedAccount(params.accountContext, 'Order submission');
4562
4292
  const result = await exchangeClient.order({
4563
4293
  orders: this.#applySubscriptionCloid(orders),
4564
4294
  grouping,
@@ -4568,9 +4298,6 @@ export class HyperLiquidProvider {
4568
4298
  throw new Error(`Order failed: ${JSON.stringify(result)}`);
4569
4299
  }
4570
4300
  const status = result.response?.data?.statuses?.[0];
4571
- if (hasProperty(status, 'error') && typeof status.error === 'string') {
4572
- throw createStatusError(status.error, result);
4573
- }
4574
4301
  // Note: `in` narrows the HyperLiquid SDK discriminated union to the
4575
4302
  // branch that has the property; `hasProperty` types the property as
4576
4303
  // `unknown`, losing downstream access to `.oid`, `.totalSz`, `.avgPx`.
@@ -4580,11 +4307,7 @@ export class HyperLiquidProvider {
4580
4307
  /* eslint-enable no-restricted-syntax */
4581
4308
  // Success - auto-rebalance excess funds
4582
4309
  if (isHip3Order && transferInfo && dexName) {
4583
- await this.#handleHip3PostOrderRebalance({
4584
- accountContext: params.accountContext,
4585
- dexName,
4586
- transferInfo,
4587
- });
4310
+ await this.#handleHip3PostOrderRebalance({ dexName, transferInfo });
4588
4311
  }
4589
4312
  return {
4590
4313
  success: true,
@@ -4600,14 +4323,9 @@ export class HyperLiquidProvider {
4600
4323
  };
4601
4324
  }
4602
4325
  catch (orderError) {
4603
- this.#recordUnsupportedAccountFromError(orderError, params.accountContext);
4604
4326
  // Failure - rollback transfer
4605
4327
  if (transferInfo && dexName) {
4606
- await this.#handleHip3OrderRollback({
4607
- accountContext: params.accountContext,
4608
- dexName,
4609
- transferInfo,
4610
- });
4328
+ await this.#handleHip3OrderRollback({ dexName, transferInfo });
4611
4329
  }
4612
4330
  throw orderError;
4613
4331
  }
@@ -4662,20 +4380,6 @@ export class HyperLiquidProvider {
4662
4380
  * @returns A promise that resolves to the result.
4663
4381
  */
4664
4382
  async placeOrder(input, _retryCount = 0, reportFailure = true) {
4665
- return await this.#placeOrder(input, _retryCount, reportFailure);
4666
- }
4667
- /**
4668
- * Place an order while preserving one account context across parent actions
4669
- * and minimum-value retries.
4670
- *
4671
- * @param input - Order parameters.
4672
- * @param _retryCount - Unused. Preserves the public call shape internally.
4673
- * @param reportFailure - Whether this call owns provider-level reporting.
4674
- * @param expectedAccountContext - Context captured by a parent action or the
4675
- * first minimum-value attempt.
4676
- * @returns The order result.
4677
- */
4678
- async #placeOrder(input, _retryCount, reportFailure, expectedAccountContext) {
4679
4383
  const deferProviderErrorReport = input.deferProviderErrorReport === true;
4680
4384
  const params = { ...input };
4681
4385
  delete params.deferProviderErrorReport;
@@ -4686,7 +4390,6 @@ export class HyperLiquidProvider {
4686
4390
  error: PERPS_ERROR_CODES.ORDER_SCALE_PREVIEW_STALE,
4687
4391
  };
4688
4392
  }
4689
- let actionAccountContext = expectedAccountContext;
4690
4393
  try {
4691
4394
  this.#deps.debugLogger.log('Placing order via HyperLiquid SDK:', params);
4692
4395
  // Basic sync validation (backward compatibility)
@@ -4714,7 +4417,7 @@ export class HyperLiquidProvider {
4714
4417
  // array, the HIP-3 transfer, and the atomic single-order submit, none of
4715
4418
  // which describe what a TWAP, a ladder, or a chase does.
4716
4419
  if (isStrategyOrderType(params.orderType)) {
4717
- return await this.#placeStrategyOrder(params, params.orderType, expectedAccountContext);
4420
+ return await this.#placeStrategyOrder(params, params.orderType);
4718
4421
  }
4719
4422
  // Extract DEX name for API calls (main DEX = null)
4720
4423
  const { dex: dexName } = parseAssetName(params.symbol);
@@ -4763,11 +4466,9 @@ export class HyperLiquidProvider {
4763
4466
  // Kept after validation so invalid orders never trigger signature prompts
4764
4467
  // (builder-fee approval, DEX abstraction enablement, etc.).
4765
4468
  const { chargesMetamaskBuilderFee } = this.#resolveOrderFeePolicy(params);
4766
- const { accountContext, builderFeeSetupContext } = await this.#ensureReadyForTrading({
4469
+ const builderFeeSetupContext = await this.#ensureReadyForTrading({
4767
4470
  requiresBuilderFee: chargesMetamaskBuilderFee,
4768
- accountContext: actionAccountContext,
4769
4471
  });
4770
- actionAccountContext = accountContext;
4771
4472
  // Debug: Log asset map state before order placement
4772
4473
  const allMapKeys = Array.from(this.#symbolToAssetId.keys());
4773
4474
  const hip3Keys = allMapKeys.filter((key) => key.includes(':'));
@@ -4825,7 +4526,6 @@ export class HyperLiquidProvider {
4825
4526
  assetId,
4826
4527
  leverage: params.leverage,
4827
4528
  marginMode: params.marginMode,
4828
- accountContext,
4829
4529
  });
4830
4530
  // 6. Handle HIP-3 balance management (if applicable)
4831
4531
  const isHip3Order = dexName !== null;
@@ -4833,7 +4533,6 @@ export class HyperLiquidProvider {
4833
4533
  if (isHip3Order && dexName) {
4834
4534
  const effectiveLeverage = params.leverage ?? assetInfo.maxLeverage ?? 1;
4835
4535
  const hip3Result = await this.#handleHip3PreOrder({
4836
- accountContext,
4837
4536
  dexName,
4838
4537
  symbol: params.symbol,
4839
4538
  orderPrice,
@@ -4876,7 +4575,6 @@ export class HyperLiquidProvider {
4876
4575
  symbol: params.symbol,
4877
4576
  assetId,
4878
4577
  chargesMetamaskBuilderFee,
4879
- accountContext,
4880
4578
  builderFeeSetupContext,
4881
4579
  });
4882
4580
  }
@@ -4909,16 +4607,15 @@ export class HyperLiquidProvider {
4909
4607
  *
4910
4608
  * @param params - Order parameters, already validated.
4911
4609
  * @param orderType - The strategy type, narrowed by the caller.
4912
- * @param expectedAccountContext - Context captured by a parent action.
4913
4610
  * @returns A promise that resolves to the result.
4914
4611
  */
4915
- async #placeStrategyOrder(params, orderType, expectedAccountContext) {
4612
+ async #placeStrategyOrder(params, orderType) {
4916
4613
  // Captured before the shared preamble, not after it: that preamble awaits
4917
4614
  // asset info, validation, trading readiness and a leverage update, and a
4918
4615
  // disconnect during any of them has to be seen before or after submission.
4919
4616
  const generation = this.#strategyGeneration;
4920
4617
  if (orderType !== 'chase') {
4921
- const context = await this.#prepareStrategyPlacement(params, expectedAccountContext);
4618
+ const context = await this.#prepareStrategyPlacement(params);
4922
4619
  return await this.#submitPreparedStrategy(params, context, async () => {
4923
4620
  if (generation !== this.#strategyGeneration) {
4924
4621
  throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
@@ -4951,7 +4648,7 @@ export class HyperLiquidProvider {
4951
4648
  this.#chasePlacementWaiters.add(placementWaiter);
4952
4649
  try {
4953
4650
  await this.#chaseTickQueue;
4954
- const context = await this.#prepareStrategyPlacement(params, expectedAccountContext);
4651
+ const context = await this.#prepareStrategyPlacement(params);
4955
4652
  return await this.#submitPreparedStrategy(params, context, async () => await this.#startChaseSession(params, context, generation));
4956
4653
  }
4957
4654
  finally {
@@ -4977,36 +4674,24 @@ export class HyperLiquidProvider {
4977
4674
  * @returns The strategy placement result.
4978
4675
  */
4979
4676
  async #submitPreparedStrategy(params, context, submit) {
4980
- const { accountContext, dexName, network, transferInfo, userAddress } = context;
4677
+ const { dexName, network, transferInfo, userAddress } = context;
4981
4678
  let result;
4982
4679
  try {
4983
4680
  result = await submit();
4984
4681
  }
4985
4682
  catch (error) {
4986
- this.#recordUnsupportedAccountFromError(error, accountContext);
4987
4683
  if (dexName && transferInfo) {
4988
- await this.#handleHip3OrderRollback({
4989
- accountContext,
4990
- dexName,
4991
- transferInfo,
4992
- });
4684
+ await this.#handleHip3OrderRollback({ dexName, transferInfo });
4993
4685
  }
4994
4686
  throw error;
4995
4687
  }
4996
- if (!result.success && result.error) {
4997
- this.#recordUnsupportedAccountFromError(new Error(result.error), accountContext);
4998
- }
4999
4688
  const resultFilledSize = new BigNumber(result.filledSize ?? 0);
5000
4689
  const hasVenueExposure = result.success === true ||
5001
4690
  result.orderId !== undefined ||
5002
4691
  (result.childOrderIds?.length ?? 0) > 0 ||
5003
4692
  (resultFilledSize.isFinite() && resultFilledSize.gt(0));
5004
4693
  if (dexName && transferInfo && !hasVenueExposure) {
5005
- await this.#handleHip3OrderRollback({
5006
- accountContext,
5007
- dexName,
5008
- transferInfo,
5009
- });
4694
+ await this.#handleHip3OrderRollback({ dexName, transferInfo });
5010
4695
  return result;
5011
4696
  }
5012
4697
  if (params.orderType === 'twap' && result.orderId) {
@@ -5017,13 +4702,7 @@ export class HyperLiquidProvider {
5017
4702
  }), {
5018
4703
  symbol: params.symbol,
5019
4704
  ...(dexName && transferInfo
5020
- ? {
5021
- hip3Transfer: {
5022
- accountContext,
5023
- dexName,
5024
- transferInfo,
5025
- },
5026
- }
4705
+ ? { hip3Transfer: { dexName, transferInfo } }
5027
4706
  : {}),
5028
4707
  });
5029
4708
  return result;
@@ -5035,11 +4714,7 @@ export class HyperLiquidProvider {
5035
4714
  transferInfo) {
5036
4715
  const session = this.#chaseSessions.get(result.orderId);
5037
4716
  if (session) {
5038
- session.hip3Transfer = {
5039
- accountContext,
5040
- dexName,
5041
- transferInfo,
5042
- };
4717
+ session.hip3Transfer = { dexName, transferInfo };
5043
4718
  return result;
5044
4719
  }
5045
4720
  }
@@ -5051,11 +4726,7 @@ export class HyperLiquidProvider {
5051
4726
  return result;
5052
4727
  }
5053
4728
  if (dexName && transferInfo) {
5054
- await this.#handleHip3PostOrderRebalance({
5055
- accountContext,
5056
- dexName,
5057
- transferInfo,
5058
- });
4729
+ await this.#handleHip3PostOrderRebalance({ dexName, transferInfo });
5059
4730
  }
5060
4731
  return result;
5061
4732
  }
@@ -5067,10 +4738,9 @@ export class HyperLiquidProvider {
5067
4738
  * so a strategy order is held to the same rules as an ordinary one.
5068
4739
  *
5069
4740
  * @param params - Order parameters.
5070
- * @param expectedAccountContext - Context captured by a parent action.
5071
4741
  * @returns The asset and sizing context the strategy handlers submit with.
5072
4742
  */
5073
- async #prepareStrategyPlacement(params, expectedAccountContext) {
4743
+ async #prepareStrategyPlacement(params) {
5074
4744
  const { dex: dexName } = parseAssetName(params.symbol);
5075
4745
  const { assetInfo, currentPrice, meta } = await this.#getAssetInfo({
5076
4746
  symbol: params.symbol,
@@ -5133,16 +4803,9 @@ export class HyperLiquidProvider {
5133
4803
  // Kept after validation so an invalid strategy order never triggers the
5134
4804
  // signature prompts in trading setup — same ordering as `placeOrder`.
5135
4805
  const { chargesMetamaskBuilderFee } = this.#resolveOrderFeePolicy(params);
5136
- const tradingActionContext = chargesMetamaskBuilderFee
5137
- ? await this.#ensureReadyForTrading({
5138
- requiresBuilderFee: true,
5139
- accountContext: expectedAccountContext,
5140
- })
5141
- : await this.#ensureReadyForTrading({
5142
- requiresBuilderFee: false,
5143
- accountContext: expectedAccountContext,
5144
- });
5145
- const { accountContext, builderFeeSetupContext } = tradingActionContext;
4806
+ const builderFeeSetupContext = chargesMetamaskBuilderFee
4807
+ ? await this.#ensureReadyForTrading({ requiresBuilderFee: true })
4808
+ : await this.#ensureReadyForTrading({ requiresBuilderFee: false });
5146
4809
  const assetId = await this.#getAssetIdWithRepair({
5147
4810
  symbol: params.symbol,
5148
4811
  dexName,
@@ -5153,19 +4816,19 @@ export class HyperLiquidProvider {
5153
4816
  assetId,
5154
4817
  leverage: params.leverage,
5155
4818
  marginMode: params.marginMode,
5156
- accountContext,
5157
4819
  });
5158
4820
  const builder = builderFeeSetupContext
5159
4821
  ? await this.#getBuilderOrderContext(builderFeeSetupContext)
5160
4822
  : undefined;
5161
- const { network, userAddress } = accountContext;
4823
+ const network = this.#clientService.isTestnetMode() ? 'testnet' : 'mainnet';
4824
+ const userAddress = builderFeeSetupContext?.userAddress ??
4825
+ (await this.#walletService.getUserAddressWithDefault());
5162
4826
  let transferInfo = null;
5163
4827
  if (dexName) {
5164
4828
  const orderPrice = ladder === undefined
5165
4829
  ? effectivePrice
5166
4830
  : Math.max(...ladder.prices.map(Number.parseFloat));
5167
4831
  const hip3Result = await this.#handleHip3PreOrder({
5168
- accountContext,
5169
4832
  dexName,
5170
4833
  symbol: params.symbol,
5171
4834
  orderPrice,
@@ -5177,7 +4840,6 @@ export class HyperLiquidProvider {
5177
4840
  transferInfo = hip3Result.transferInfo;
5178
4841
  }
5179
4842
  return {
5180
- accountContext,
5181
4843
  assetId,
5182
4844
  szDecimals: assetInfo.szDecimals,
5183
4845
  formattedSize,
@@ -5265,7 +4927,7 @@ export class HyperLiquidProvider {
5265
4927
  * @returns A promise that resolves to the result.
5266
4928
  */
5267
4929
  async #placeTwapOrder(params, context, generation) {
5268
- const { accountContext, assetId, formattedSize } = context;
4930
+ const { assetId, formattedSize } = context;
5269
4931
  const durationMinutes = params.twapDuration;
5270
4932
  if (durationMinutes === undefined) {
5271
4933
  throw new Error(PERPS_ERROR_CODES.ORDER_TWAP_DURATION_REQUIRED);
@@ -5283,7 +4945,6 @@ export class HyperLiquidProvider {
5283
4945
  durationMinutes,
5284
4946
  randomize: params.twapRandomize ?? false,
5285
4947
  });
5286
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'TWAP order submission');
5287
4948
  const result = await exchangeClient.twapOrder({
5288
4949
  twap: {
5289
4950
  a: assetId,
@@ -5313,7 +4974,6 @@ export class HyperLiquidProvider {
5313
4974
  if (generation !== this.#strategyGeneration) {
5314
4975
  let remainsLive = true;
5315
4976
  try {
5316
- await this.#assertRollbackAccountWritable(accountContext);
5317
4977
  const cancelResult = await exchangeClient.twapCancel({
5318
4978
  a: assetId,
5319
4979
  t: running.twapId,
@@ -5361,7 +5021,7 @@ export class HyperLiquidProvider {
5361
5021
  * @returns A promise that resolves to the result.
5362
5022
  */
5363
5023
  async #placeScaleOrder(params, context, generation) {
5364
- const { accountContext, assetId, formattedSize, ladder, builder } = context;
5024
+ const { assetId, formattedSize, ladder, builder } = context;
5365
5025
  // Built and validated in `#prepareStrategyPlacement`, before anything was
5366
5026
  // signed, so what is submitted here is exactly what the minimums were
5367
5027
  // applied to.
@@ -5402,7 +5062,6 @@ export class HyperLiquidProvider {
5402
5062
  let result;
5403
5063
  let thrownBulkOrderError;
5404
5064
  try {
5405
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Scale order submission');
5406
5065
  result = await exchangeClient.order({
5407
5066
  orders,
5408
5067
  grouping: 'na',
@@ -5419,7 +5078,6 @@ export class HyperLiquidProvider {
5419
5078
  }
5420
5079
  const rawStatuses = result.response?.data?.statuses;
5421
5080
  const statuses = Array.isArray(rawStatuses) ? rawStatuses : [];
5422
- const isMultiSigRequired = this.#recordUnsupportedAccountFromStatuses(statuses, result, accountContext);
5423
5081
  const outcomes = Array.from({ length: count }, (_unused, index) => parseScaleBulkOrderStatus(statuses[index]));
5424
5082
  const acceptedCount = outcomes.filter((outcome) => outcome?.kind === 'accepted').length;
5425
5083
  const everyStatusClassified = outcomes.every((outcome) => outcome !== undefined);
@@ -5472,19 +5130,6 @@ export class HyperLiquidProvider {
5472
5130
  ...acceptedResult,
5473
5131
  });
5474
5132
  const cancelNonRejectedOrders = async () => {
5475
- try {
5476
- await this.#assertRollbackAccountWritable(accountContext);
5477
- }
5478
- catch (error) {
5479
- this.#deps.debugLogger.log('Scale placement rollback not signed after its account context changed', {
5480
- error: ensureError(error, 'HyperLiquidProvider.placeScaleOrder.rollback').message,
5481
- groupId,
5482
- });
5483
- return {
5484
- orderIds: restingChildOrderIds,
5485
- clientOrderIds: cleanupClientOrderIds,
5486
- };
5487
- }
5488
5133
  const [orderIds, pendingClientOrderIds] = await Promise.all([
5489
5134
  this.#cancelOrderRequests(exchangeClient, restingChildOrderIds.map((orderId) => ({
5490
5135
  a: assetId,
@@ -5574,9 +5219,7 @@ export class HyperLiquidProvider {
5574
5219
  childOrderIds: [],
5575
5220
  });
5576
5221
  }
5577
- throw new Error(isMultiSigRequired
5578
- ? PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED
5579
- : PERPS_ERROR_CODES.ORDER_REJECTED);
5222
+ throw new Error(PERPS_ERROR_CODES.ORDER_REJECTED);
5580
5223
  }
5581
5224
  this.#scaleOrderGroups.set(groupId, {
5582
5225
  symbol: params.symbol,
@@ -5597,7 +5240,7 @@ export class HyperLiquidProvider {
5597
5240
  * @returns A promise that resolves to the result.
5598
5241
  */
5599
5242
  async #startChaseSession(params, context, generation) {
5600
- const { accountContext, assetId, szDecimals, formattedSize, builder } = context;
5243
+ const { assetId, szDecimals, formattedSize, builder } = context;
5601
5244
  // Captured now, alongside the builder fee and for the same reason: the fee
5602
5245
  // resolution behind it is cleared when the caller's `placeOrder` returns,
5603
5246
  // which for a chase is before any replacement runs.
@@ -5645,7 +5288,6 @@ export class HyperLiquidProvider {
5645
5288
  reduceOnly: params.reduceOnly ?? false,
5646
5289
  builder,
5647
5290
  marksSubscriptionCloid,
5648
- accountContext,
5649
5291
  exchangeClient: placingClient,
5650
5292
  });
5651
5293
  break;
@@ -5679,7 +5321,6 @@ export class HyperLiquidProvider {
5679
5321
  const intervalMs = params.chaseIntervalMs ?? CHASE_ORDER_CONFIG.DefaultIntervalMs;
5680
5322
  const sessionId = generatePerpsId('chase');
5681
5323
  const session = {
5682
- accountContext,
5683
5324
  symbol: params.symbol,
5684
5325
  assetId,
5685
5326
  isBuy: params.isBuy,
@@ -5814,12 +5455,9 @@ export class HyperLiquidProvider {
5814
5455
  * than looked up here so a first placement can keep the instance it signed
5815
5456
  * with, which is the only one that can take the order back once `disconnect`
5816
5457
  * has dropped the service's reference.
5817
- * @param params.accountContext - Account/network/lifecycle captured by the
5818
- * action that owns the chase.
5819
5458
  * @returns The resting order's exchange ID.
5820
5459
  */
5821
5460
  async #restChaseOrder(params) {
5822
- await this.#assertNoKnownUnsupportedAccount(params.accountContext, 'Chase order submission');
5823
5461
  const result = await params.exchangeClient.order({
5824
5462
  orders: this.#applySubscriptionCloid([
5825
5463
  {
@@ -5860,15 +5498,6 @@ export class HyperLiquidProvider {
5860
5498
  this.#chaseTickQueue = this.#chaseTickQueue
5861
5499
  .then(() => this.#runChaseTick(sessionId))
5862
5500
  .catch(async (error) => {
5863
- const failedSession = this.#chaseSessions.get(sessionId);
5864
- const accountUnsupported = failedSession
5865
- ? this.#recordUnsupportedAccountFromError(error, failedSession.accountContext)
5866
- : false;
5867
- if (accountUnsupported) {
5868
- this.#stopChaseSession(sessionId);
5869
- this.#deps.debugLogger.log('Chase stopped after account support correction', { sessionId });
5870
- return;
5871
- }
5872
5501
  // A rejected agent is dropped so the next tick asks for another.
5873
5502
  await this.#evictRejectedAgent(error);
5874
5503
  // Resolve the shared queue after every failure. Otherwise one
@@ -6183,7 +5812,8 @@ export class HyperLiquidProvider {
6183
5812
  reduceOnly: session.reduceOnly,
6184
5813
  builder: session.builder,
6185
5814
  marksSubscriptionCloid: session.marksSubscriptionCloid,
6186
- accountContext: session.accountContext,
5815
+ // A running session is on a live provider, so the current client is
5816
+ // the right one; only the first placement has a teardown to survive.
6187
5817
  exchangeClient: this.#clientService.getExchangeClient(),
6188
5818
  });
6189
5819
  session.size = remaining;
@@ -6415,9 +6045,6 @@ export class HyperLiquidProvider {
6415
6045
  }
6416
6046
  // Looked up only once there is something to cancel, so a session with
6417
6047
  // nothing resting still answers on a provider whose client is already gone.
6418
- if (!placingClient) {
6419
- await this.#assertNoKnownUnsupportedAccount(session.accountContext, 'Chase cancellation');
6420
- }
6421
6048
  const exchangeClient = placingClient ?? this.#clientService.getExchangeClient();
6422
6049
  let result;
6423
6050
  try {
@@ -6426,7 +6053,6 @@ export class HyperLiquidProvider {
6426
6053
  });
6427
6054
  }
6428
6055
  catch (error) {
6429
- this.#recordUnsupportedAccountFromError(error, session.accountContext);
6430
6056
  const message = ensureError(error, 'HyperLiquidProvider.cancelChaseChild').message.toLowerCase();
6431
6057
  if (ALREADY_GONE_CANCEL_MARKERS.some((marker) => message.includes(marker))) {
6432
6058
  return CancelChildOutcome.Gone;
@@ -6434,9 +6060,6 @@ export class HyperLiquidProvider {
6434
6060
  throw error;
6435
6061
  }
6436
6062
  const status = result.response?.data?.statuses?.[0];
6437
- if (isStatusObject(status) && typeof status.error === 'string') {
6438
- this.#recordUnsupportedAccountFromError(createStatusError(status.error, result), session.accountContext);
6439
- }
6440
6063
  const signerFailure = await this.#classifyStatusSignerFailure([status], result);
6441
6064
  if (signerFailure) {
6442
6065
  throw signerFailure;
@@ -6635,16 +6258,7 @@ export class HyperLiquidProvider {
6635
6258
  }
6636
6259
  let { rebalancePromise } = trackedOrder;
6637
6260
  if (!rebalancePromise) {
6638
- const { hip3Transfer } = trackedOrder;
6639
- rebalancePromise = this.#handleHip3PostOrderRebalance({
6640
- ...hip3Transfer,
6641
- accountContext: {
6642
- ...hip3Transfer.accountContext,
6643
- // TWAP tracking deliberately survives reconnects. Keep its stable
6644
- // account/network scope, but bind the cleanup write to this lifecycle.
6645
- lifecycleGeneration: this.#lifecycleGeneration,
6646
- },
6647
- });
6261
+ rebalancePromise = this.#handleHip3PostOrderRebalance(trackedOrder.hip3Transfer);
6648
6262
  trackedOrder.rebalancePromise = rebalancePromise;
6649
6263
  }
6650
6264
  try {
@@ -6897,10 +6511,9 @@ export class HyperLiquidProvider {
6897
6511
  if (!/^\d+$/u.test(params.orderId) || !Number.isSafeInteger(twapId)) {
6898
6512
  throw new Error(PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
6899
6513
  }
6900
- const { accountContext } = await this.#ensureReadyForTrading({
6901
- requiresBuilderFee: false,
6902
- });
6903
- const { network, userAddress } = accountContext;
6514
+ await this.#ensureReadyForTrading({ requiresBuilderFee: false });
6515
+ const network = this.#clientService.isTestnetMode() ? 'testnet' : 'mainnet';
6516
+ const userAddress = await this.#walletService.getUserAddressWithDefault();
6904
6517
  const trackingKey = getTwapOrderScopeKey({
6905
6518
  network,
6906
6519
  userAddress,
@@ -6936,7 +6549,6 @@ export class HyperLiquidProvider {
6936
6549
  symbol: params.symbol,
6937
6550
  dexName: parseAssetName(params.symbol).dex,
6938
6551
  });
6939
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'TWAP cancellation');
6940
6552
  const result = await this.#clientService.getExchangeClient().twapCancel({
6941
6553
  a: assetId,
6942
6554
  t: twapId,
@@ -6953,7 +6565,7 @@ export class HyperLiquidProvider {
6953
6565
  const rawError = isStatusObject(status) && typeof status.error === 'string'
6954
6566
  ? status.error
6955
6567
  : 'TWAP cancellation failed';
6956
- return createErrorResult(await this.#mapStatusError(rawError, result, accountContext), {
6568
+ return createErrorResult(await this.#mapStatusError(rawError, result), {
6957
6569
  success: false,
6958
6570
  orderId: params.orderId,
6959
6571
  });
@@ -6969,9 +6581,7 @@ export class HyperLiquidProvider {
6969
6581
  if (!group) {
6970
6582
  throw new Error(PERPS_ERROR_CODES.ORDER_STRATEGY_HANDLE_UNKNOWN);
6971
6583
  }
6972
- const { accountContext } = await this.#ensureReadyForTrading({
6973
- requiresBuilderFee: false,
6974
- });
6584
+ await this.#ensureReadyForTrading({ requiresBuilderFee: false });
6975
6585
  const assetId = await this.#getAssetIdWithRepair({
6976
6586
  symbol: group.symbol,
6977
6587
  dexName: parseAssetName(group.symbol).dex,
@@ -6985,14 +6595,9 @@ export class HyperLiquidProvider {
6985
6595
  asset: assetId,
6986
6596
  cloid: clientOrderId,
6987
6597
  }));
6988
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Scale cancellation');
6989
6598
  const [orderCancellation, cloidCancellation] = await Promise.all([
6990
- this.#cancelOrderRequestBatch(exchangeClient, cancelRequests, (error) => {
6991
- this.#recordUnsupportedAccountFromError(error, accountContext);
6992
- }),
6993
- this.#cancelOrderCloidRequestBatch(exchangeClient, cancelByCloidRequests, (error) => {
6994
- this.#recordUnsupportedAccountFromError(error, accountContext);
6995
- }),
6599
+ this.#cancelOrderRequestBatch(exchangeClient, cancelRequests),
6600
+ this.#cancelOrderCloidRequestBatch(exchangeClient, cancelByCloidRequests),
6996
6601
  ]);
6997
6602
  const remaining = orderCancellation.remainingOrderIds.map(String);
6998
6603
  const { remainingClientOrderIds } = cloidCancellation;
@@ -7092,18 +6697,7 @@ export class HyperLiquidProvider {
7092
6697
  }
7093
6698
  return { success: true, orderId: params.orderId };
7094
6699
  }
7095
- this.#assertProviderLifecycleCurrent(session.accountContext.lifecycleGeneration, 'Chase cancellation setup');
7096
- const currentAddress = await this.#walletService.getUserAddressWithDefault();
7097
- if (currentAddress.toLowerCase() !==
7098
- session.accountContext.userAddress.toLowerCase() ||
7099
- (this.#clientService.isTestnetMode() ? 'testnet' : 'mainnet') !==
7100
- session.accountContext.network) {
7101
- throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
7102
- }
7103
- await this.#ensureReadyForTrading({
7104
- requiresBuilderFee: false,
7105
- accountContext: session.accountContext,
7106
- });
6700
+ await this.#ensureReadyForTrading({ requiresBuilderFee: false });
7107
6701
  // Only a refusal leaves an order behind. A child that had already filled or
7108
6702
  // been cancelled is reported as a rejection too, but nothing of it is
7109
6703
  // resting — treating that as a failure would pin the handle open forever on
@@ -7147,11 +6741,11 @@ export class HyperLiquidProvider {
7147
6741
  *
7148
6742
  * @param exchangeClient - Client that owns the orders.
7149
6743
  * @param requests - Venue cancel-by-CLOID requests.
7150
- * @param onError - Optional operation-specific failure classifier.
7151
6744
  * @returns Client order IDs that may still be resting.
7152
6745
  */
7153
- async #cancelOrderCloidRequests(exchangeClient, requests, onError) {
7154
- return (await this.#cancelOrderCloidRequestBatch(exchangeClient, requests, onError)).remainingClientOrderIds;
6746
+ async #cancelOrderCloidRequests(exchangeClient, requests) {
6747
+ return (await this.#cancelOrderCloidRequestBatch(exchangeClient, requests))
6748
+ .remainingClientOrderIds;
7155
6749
  }
7156
6750
  /**
7157
6751
  * Cancel pending orders by client order ID, reporting a signer that could
@@ -7159,20 +6753,14 @@ export class HyperLiquidProvider {
7159
6753
  *
7160
6754
  * @param exchangeClient - Client that owns the orders.
7161
6755
  * @param requests - Venue cancel-by-CLOID requests.
7162
- * @param onError - Optional operation-specific failure classifier.
7163
6756
  * @returns Client order IDs that may still be resting, and the signer
7164
6757
  * failure when the signer could not sign the request.
7165
6758
  */
7166
- async #cancelOrderCloidRequestBatch(exchangeClient, requests, onError) {
6759
+ async #cancelOrderCloidRequestBatch(exchangeClient, requests) {
7167
6760
  if (requests.length === 0) {
7168
6761
  return { remainingClientOrderIds: [] };
7169
6762
  }
7170
6763
  const classifyStatuses = async (statuses, answer) => {
7171
- for (const status of statuses) {
7172
- if (isStatusObject(status) && typeof status.error === 'string') {
7173
- await onError?.(createStatusError(status.error, answer));
7174
- }
7175
- }
7176
6764
  // A signer failure is classified (and reported) once for the request;
7177
6765
  // entries the venue cancelled are not resting either way.
7178
6766
  const signerFailure = await this.#classifyStatusSignerFailure(statuses, answer);
@@ -7196,7 +6784,6 @@ export class HyperLiquidProvider {
7196
6784
  return await classifyStatuses(statuses, result);
7197
6785
  }
7198
6786
  catch (error) {
7199
- await onError?.(error);
7200
6787
  // The SDK throws when any entry failed, with every entry's status.
7201
6788
  const statuses = getCancelStatusesFromError(error, requests.length);
7202
6789
  if (statuses) {
@@ -7237,11 +6824,6 @@ export class HyperLiquidProvider {
7237
6824
  };
7238
6825
  }
7239
6826
  const classifyStatuses = async (statuses, answer) => {
7240
- for (const status of statuses) {
7241
- if (isStatusObject(status) && typeof status.error === 'string') {
7242
- await onError?.(createStatusError(status.error, answer));
7243
- }
7244
- }
7245
6827
  // A signer failure is classified (and reported) once for the request;
7246
6828
  // entries the venue cancelled still count as cancelled.
7247
6829
  const signerFailure = await this.#classifyStatusSignerFailure(statuses, answer);
@@ -7387,11 +6969,9 @@ export class HyperLiquidProvider {
7387
6969
  * Apply both signer invalidation and account-shape classification to writes.
7388
6970
  *
7389
6971
  * @param error - Failed protection write.
7390
- * @param accountContext - Account and network that authorized the write.
7391
6972
  * @returns Actionable error retaining the venue message when unmapped.
7392
6973
  */
7393
- async #classifyProtectionWriteError(error, accountContext) {
7394
- this.#recordUnsupportedAccountFromError(error, accountContext);
6974
+ async #classifyProtectionWriteError(error) {
7395
6975
  return ((await this.#classifySignerFailure(error)) ??
7396
6976
  (isHyperLiquidMultiSigRequiredError(error)
7397
6977
  ? new Error(PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED)
@@ -7798,30 +7378,19 @@ export class HyperLiquidProvider {
7798
7378
  // Every refusal is behind us, so shared trading readiness can run now.
7799
7379
  // HyperLiquid's modify action has no builder field and must not request a
7800
7380
  // builder-fee approval.
7801
- const { accountContext } = await this.#ensureReadyForTrading({
7802
- requiresBuilderFee: false,
7803
- });
7381
+ await this.#ensureReadyForTrading({ requiresBuilderFee: false });
7804
7382
  // Submit modification via SDK. The cloid is deliberately left unmarked:
7805
7383
  // `modify` carries no builder field, as the readiness call above records,
7806
7384
  // so no MetaMask fee is charged on this action and marking it would tell
7807
7385
  // the fill fan-out a reduction applied to an order that paid nothing.
7808
7386
  // The replacement inherits the resting order's own attribution.
7809
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Order modification');
7810
7387
  const exchangeClient = this.#clientService.getExchangeClient();
7811
- try {
7812
- const result = await exchangeClient.modify({
7813
- oid: typeof params.orderId === 'string'
7814
- ? params.orderId
7815
- : params.orderId,
7816
- order: newOrder,
7817
- });
7818
- if (result.status !== 'ok') {
7819
- throw new Error(`Order modification failed: ${JSON.stringify(result)}`);
7820
- }
7821
- }
7822
- catch (error) {
7823
- this.#recordUnsupportedAccountFromError(error, accountContext);
7824
- throw error;
7388
+ const result = await exchangeClient.modify({
7389
+ oid: typeof params.orderId === 'string' ? params.orderId : params.orderId,
7390
+ order: newOrder,
7391
+ });
7392
+ if (result.status !== 'ok') {
7393
+ throw new Error(`Order modification failed: ${JSON.stringify(result)}`);
7825
7394
  }
7826
7395
  // `params.orderId` is the order that was just REPLACED, so returning it
7827
7396
  // as OrderResult.orderId (documented as the exchange order ID) names an
@@ -7881,7 +7450,6 @@ export class HyperLiquidProvider {
7881
7450
  // current child instead of leaving a replacement chasing behind the user.
7882
7451
  const owningChase = [...this.#chaseSessions.entries()].find(([, session]) => session.orderId === params.orderId ||
7883
7452
  session.replacingOrderId === params.orderId);
7884
- let accountContext;
7885
7453
  try {
7886
7454
  if (owningChase) {
7887
7455
  this.#stopChaseSession(owningChase[0]);
@@ -7904,15 +7472,12 @@ export class HyperLiquidProvider {
7904
7472
  if (!coinValidation.isValid) {
7905
7473
  throw new Error(coinValidation.error);
7906
7474
  }
7907
- ({ accountContext } = await this.#ensureReadyForTrading({
7908
- requiresBuilderFee: false,
7909
- }));
7475
+ await this.#ensureReadyForTrading({ requiresBuilderFee: false });
7476
+ const exchangeClient = this.#clientService.getExchangeClient();
7910
7477
  const asset = await this.#getAssetIdWithRepair({
7911
7478
  symbol: params.symbol,
7912
7479
  dexName: parseAssetName(params.symbol).dex,
7913
7480
  });
7914
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Order cancellation');
7915
- const exchangeClient = this.#clientService.getExchangeClient();
7916
7481
  const result = await exchangeClient.cancel({
7917
7482
  cancels: [
7918
7483
  {
@@ -7935,15 +7500,12 @@ export class HyperLiquidProvider {
7935
7500
  const rawError = isStatusObject(status) && typeof status.error === 'string'
7936
7501
  ? status.error
7937
7502
  : 'Order cancellation failed';
7938
- return createErrorResult(await this.#mapStatusError(rawError, result, accountContext), {
7503
+ return createErrorResult(await this.#mapStatusError(rawError, result), {
7939
7504
  success: false,
7940
7505
  orderId: params.orderId,
7941
7506
  });
7942
7507
  }
7943
7508
  catch (error) {
7944
- if (accountContext) {
7945
- this.#recordUnsupportedAccountFromError(error, accountContext);
7946
- }
7947
7509
  const mappedError = await this.#reportWriteError(error, 'cancelOrder', {
7948
7510
  orderId: params.orderId,
7949
7511
  coin: params.symbol,
@@ -8015,9 +7577,8 @@ export class HyperLiquidProvider {
8015
7577
  if (ordinaryOrders.length > 0) {
8016
7578
  // Cancellation carries no builder context, so it must not prompt for a
8017
7579
  // fee approval that the action cannot use.
8018
- const { accountContext } = await this.#ensureReadyForTrading({
8019
- requiresBuilderFee: false,
8020
- });
7580
+ await this.#ensureReadyForTrading({ requiresBuilderFee: false });
7581
+ const exchangeClient = this.#clientService.getExchangeClient();
8021
7582
  const cancelRequests = await Promise.all(ordinaryOrders.map(async ({ order }) => {
8022
7583
  const asset = await this.#getAssetIdWithRepair({
8023
7584
  symbol: order.symbol,
@@ -8028,8 +7589,6 @@ export class HyperLiquidProvider {
8028
7589
  o: parseInt(order.orderId, 10),
8029
7590
  };
8030
7591
  }));
8031
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Batch order cancellation');
8032
- const exchangeClient = this.#clientService.getExchangeClient();
8033
7592
  let statuses;
8034
7593
  let answer;
8035
7594
  try {
@@ -8064,7 +7623,6 @@ export class HyperLiquidProvider {
8064
7623
  : undefined;
8065
7624
  let error = PERPS_ERROR_CODES.BATCH_CANCEL_FAILED;
8066
7625
  if (statusError) {
8067
- this.#recordUnsupportedAccountFromError(statusError, accountContext);
8068
7626
  error =
8069
7627
  signerFailure && (await this.#isSignerFailure(statusError))
8070
7628
  ? signerFailure.message
@@ -8108,14 +7666,11 @@ export class HyperLiquidProvider {
8108
7666
  async closePositions(params) {
8109
7667
  // Declare outside try block so it's accessible in catch block
8110
7668
  let positionsToClose = [];
8111
- let accountContext;
8112
7669
  const unavailableResults = [];
8113
7670
  try {
8114
7671
  // Batch preparation needs trading readiness but not builder approval yet.
8115
7672
  // The provider-owned market policy is resolved from the positions below.
8116
- ({ accountContext } = await this.#ensureReadyForTrading({
8117
- requiresBuilderFee: false,
8118
- }));
7673
+ await this.#ensureReadyForTrading({ requiresBuilderFee: false });
8119
7674
  // Selected symbols only need the DEXes they belong to. Loading every
8120
7675
  // market group would refuse a BTC close when an unrelated HIP-3 DEX
8121
7676
  // cannot be read. closeAll / empty symbols still need a complete
@@ -8186,6 +7741,8 @@ export class HyperLiquidProvider {
8186
7741
  results: unavailableResults,
8187
7742
  };
8188
7743
  }
7744
+ // Get exchange client for order submission
7745
+ const exchangeClient = this.#clientService.getExchangeClient();
8189
7746
  // Pre-fetch meta for all unique DEXs to avoid N API calls in loop
8190
7747
  const uniqueDexs = [
8191
7748
  ...new Set(positionsToClose.map((pos) => parseAssetName(pos.symbol).dex ?? 'main')),
@@ -8300,16 +7857,10 @@ export class HyperLiquidProvider {
8300
7857
  // policy charges, preserving deterministic behavior for future policies
8301
7858
  // that vary by market or route.
8302
7859
  const chargesMetamaskBuilderFee = feePolicyContexts.some((context) => this.#resolveOrderFeePolicy(context).chargesMetamaskBuilderFee);
8303
- let builder;
8304
- if (chargesMetamaskBuilderFee) {
8305
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Batch close setup');
8306
- const builderFeeSetupContext = await this.#ensureBuilderFeeSetup(undefined, {}, accountContext);
8307
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Batch close setup');
8308
- builder = await this.#getBuilderOrderContext(builderFeeSetupContext);
8309
- }
7860
+ const builder = chargesMetamaskBuilderFee
7861
+ ? await this.#getBuilderOrderContext(await this.#ensureReadyForTrading({ requiresBuilderFee: true }))
7862
+ : undefined;
8310
7863
  // Single batch API call
8311
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Batch close submission');
8312
- const exchangeClient = this.#clientService.getExchangeClient();
8313
7864
  const result = await exchangeClient.order({
8314
7865
  orders: this.#applySubscriptionCloid(orders),
8315
7866
  grouping: 'na',
@@ -8317,11 +7868,6 @@ export class HyperLiquidProvider {
8317
7868
  });
8318
7869
  // Parse response statuses (one per order)
8319
7870
  const { statuses } = result.response.data;
8320
- for (const status of statuses) {
8321
- if (hasProperty(status, 'error') && typeof status.error === 'string') {
8322
- this.#recordUnsupportedAccountFromError(createStatusError(status.error, result), accountContext);
8323
- }
8324
- }
8325
7871
  const successCount = statuses.filter((stat) => isStatusObject(stat) &&
8326
7872
  (hasProperty(stat, 'filled') || hasProperty(stat, 'resting'))).length;
8327
7873
  const failureCount = statuses.length -
@@ -8340,7 +7886,6 @@ export class HyperLiquidProvider {
8340
7886
  this.#deps.debugLogger.log('Position closed successfully, initiating manual auto-transfer back', { symbol: orderedPositions[i].symbol, freedMargin });
8341
7887
  // Non-blocking: Transfer freed margin back to main DEX
8342
7888
  await this.#autoTransferBackAfterClose({
8343
- accountContext,
8344
7889
  sourceDex,
8345
7890
  freedMargin,
8346
7891
  });
@@ -8376,9 +7921,6 @@ export class HyperLiquidProvider {
8376
7921
  };
8377
7922
  }
8378
7923
  catch (error) {
8379
- if (accountContext) {
8380
- this.#recordUnsupportedAccountFromError(error, accountContext);
8381
- }
8382
7924
  const signerFailure = await this.#handleSignerFailure(error, 'closePositions', {
8383
7925
  positionCount: positionsToClose.length,
8384
7926
  });
@@ -8462,11 +8004,6 @@ export class HyperLiquidProvider {
8462
8004
  const lifecycle = this.#lifecycleGeneration;
8463
8005
  const isTestnet = this.#clientService.isTestnetMode();
8464
8006
  const userAddress = await this.#walletService.getUserAddressWithDefault();
8465
- const accountContext = {
8466
- lifecycleGeneration: lifecycle,
8467
- network: isTestnet ? 'testnet' : 'mainnet',
8468
- userAddress,
8469
- };
8470
8007
  const assertScope = async () => {
8471
8008
  const current = await this.#walletService.getUserAddressWithDefault();
8472
8009
  this.#assertProviderLifecycleCurrent(lifecycle, 'updatePositionTPSL');
@@ -8862,18 +8399,12 @@ export class HyperLiquidProvider {
8862
8399
  const requiresBuilderFee = replacementChargesMetamaskBuilderFee || rollbackRequiresBuilderFee;
8863
8400
  // Approval and builder-context resolution both finish before the
8864
8401
  // pre-cancel. A failure here therefore leaves the old protection intact.
8865
- await assertScope();
8866
- const tradingActionContext = requiresBuilderFee
8402
+ const builderFeeSetupContext = requiresBuilderFee
8867
8403
  ? await this.#ensureReadyForTrading({
8868
8404
  requiresBuilderFee: true,
8869
8405
  builderFeeApprovalFailureCode: PERPS_ERROR_CODES.TPSL_UPDATE_FAILED,
8870
- accountContext,
8871
8406
  })
8872
- : await this.#ensureReadyForTrading({
8873
- requiresBuilderFee: false,
8874
- accountContext,
8875
- });
8876
- const { builderFeeSetupContext } = tradingActionContext;
8407
+ : await this.#ensureReadyForTrading({ requiresBuilderFee: false });
8877
8408
  const builderOrderContext = builderFeeSetupContext
8878
8409
  ? await this.#getBuilderOrderContext(builderFeeSetupContext)
8879
8410
  : undefined;
@@ -8918,10 +8449,9 @@ export class HyperLiquidProvider {
8918
8449
  builderOrderContext && { builder: builderOrderContext }),
8919
8450
  });
8920
8451
  statuses = result.response?.data?.statuses ?? [];
8921
- this.#recordUnsupportedAccountFromStatuses(statuses, result, accountContext);
8922
8452
  }
8923
8453
  catch (error) {
8924
- const failure = await this.#classifyProtectionWriteError(error, accountContext);
8454
+ const failure = await this.#classifyProtectionWriteError(error);
8925
8455
  restorationFailure = failure;
8926
8456
  statuses =
8927
8457
  this.#readProtectionErrorStatuses(error) ??
@@ -8975,10 +8505,10 @@ export class HyperLiquidProvider {
8975
8505
  await assertCurrentPosition();
8976
8506
  const oldCancellation = await this.#cancelOrderRequestBatch(exchangeClient, cancelRequests, async (error) => {
8977
8507
  if (cancelGuardFailure !== undefined) {
8978
- throw await this.#classifyProtectionWriteError(cancelGuardFailure, accountContext);
8508
+ throw await this.#classifyProtectionWriteError(cancelGuardFailure);
8979
8509
  }
8980
8510
  if (isHyperLiquidMultiSigRequiredError(error)) {
8981
- throw await this.#classifyProtectionWriteError(error, accountContext);
8511
+ throw await this.#classifyProtectionWriteError(error);
8982
8512
  }
8983
8513
  });
8984
8514
  // The signer could not sign the cancel. With nothing cancelled the old
@@ -9076,12 +8606,11 @@ export class HyperLiquidProvider {
9076
8606
  builderOrderContext && { builder: builderOrderContext }),
9077
8607
  });
9078
8608
  placementStatuses = result.response?.data?.statuses ?? [];
9079
- this.#recordUnsupportedAccountFromStatuses(placementStatuses, result, accountContext);
9080
8609
  responseAccepted = result.status === 'ok';
9081
8610
  }
9082
8611
  catch (error) {
9083
8612
  // Drop and report a rejected agent before restoration signs.
9084
- placementFailure = await this.#classifyProtectionWriteError(error, accountContext);
8613
+ placementFailure = await this.#classifyProtectionWriteError(error);
9085
8614
  await assertScope();
9086
8615
  const statuses = this.#readProtectionErrorStatuses(error);
9087
8616
  if (statuses !== undefined) {
@@ -9197,7 +8726,7 @@ export class HyperLiquidProvider {
9197
8726
  error.message === PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED)) {
9198
8727
  // Old protection has already changed. Classification must not
9199
8728
  // throw away this batch's still-live replacement identities.
9200
- cleanupFailure = await this.#classifyProtectionWriteError(error, accountContext);
8729
+ cleanupFailure = await this.#classifyProtectionWriteError(error);
9201
8730
  }
9202
8731
  });
9203
8732
  await assertScope();
@@ -9275,9 +8804,7 @@ export class HyperLiquidProvider {
9275
8804
  this.#deps.debugLogger.log('Closing position:', params);
9276
8805
  // The delegated placeOrder call resolves the builder-fee policy for the
9277
8806
  // concrete close order after validation.
9278
- const { accountContext } = await this.#ensureReadyForTrading({
9279
- requiresBuilderFee: false,
9280
- });
8807
+ await this.#ensureReadyForTrading({ requiresBuilderFee: false });
9281
8808
  // A caller snapshot is never authoritative for a reduce-only close. Use
9282
8809
  // the current DEX slice or its HTTP fallback, and fail closed if neither
9283
8810
  // answers. This keeps the size and side on one freshness contract across
@@ -9356,8 +8883,7 @@ export class HyperLiquidProvider {
9356
8883
  // position.
9357
8884
  const isFullClose = closeSizeNum >= absPositionSize;
9358
8885
  // Execute position close with consistent slippage handling
9359
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Position close');
9360
- const result = await this.#placeOrder({
8886
+ const result = await this.placeOrder({
9361
8887
  symbol: params.symbol,
9362
8888
  isBuy,
9363
8889
  size: closeSize,
@@ -9377,7 +8903,7 @@ export class HyperLiquidProvider {
9377
8903
  usdAmount: isFullClose ? undefined : params.usdAmount,
9378
8904
  priceAtCalculation: params.priceAtCalculation,
9379
8905
  maxSlippageBps: params.maxSlippageBps,
9380
- }, 0, false, accountContext);
8906
+ }, 0, false);
9381
8907
  // Return freed margin using native abstraction or programmatic transfer
9382
8908
  if (result.success &&
9383
8909
  isHip3Position &&
@@ -9386,7 +8912,6 @@ export class HyperLiquidProvider {
9386
8912
  this.#deps.debugLogger.log('Position closed successfully, initiating manual auto-transfer back');
9387
8913
  // Non-blocking: Transfer freed margin back to main DEX
9388
8914
  await this.#autoTransferBackAfterClose({
9389
- accountContext,
9390
8915
  sourceDex: hip3Dex,
9391
8916
  freedMargin,
9392
8917
  });
@@ -9429,13 +8954,11 @@ export class HyperLiquidProvider {
9429
8954
  * - ntli: Amount in micro-units (amount * 1e6)
9430
8955
  */
9431
8956
  async updateMargin(params) {
9432
- let accountContext;
9433
8957
  try {
9434
8958
  this.#deps.debugLogger.log('Updating position margin:', params);
9435
8959
  const { symbol, amount } = params;
9436
8960
  // Ensure provider is ready
9437
8961
  await this.#ensureReady();
9438
- accountContext = await this.#assertAccountSupported();
9439
8962
  // Use the target DEX's current slice or one targeted HTTP read before
9440
8963
  // deriving the position side.
9441
8964
  const positions = await this.#getPositionsForOperation(parseAssetName(symbol).dex ?? '');
@@ -9471,7 +8994,6 @@ export class HyperLiquidProvider {
9471
8994
  }
9472
8995
  }
9473
8996
  // Call SDK to update isolated margin
9474
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Margin update');
9475
8997
  const exchangeClient = this.#clientService.getExchangeClient();
9476
8998
  const result = await exchangeClient.updateIsolatedMargin({
9477
8999
  asset: assetId,
@@ -9487,18 +9009,13 @@ export class HyperLiquidProvider {
9487
9009
  };
9488
9010
  }
9489
9011
  catch (error) {
9490
- const isMultiSigRequired = accountContext
9491
- ? this.#recordUnsupportedAccountFromError(error, accountContext)
9492
- : false;
9493
9012
  const signerFailure = await this.#handleSignerFailure(error, 'updateMargin', {
9494
9013
  symbol: params.symbol,
9495
9014
  });
9496
9015
  if (signerFailure) {
9497
9016
  return { success: false, error: signerFailure.message };
9498
9017
  }
9499
- const safeError = isMultiSigRequired
9500
- ? new Error(PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED)
9501
- : ensureError(error, 'HyperLiquidProvider.updateMargin');
9018
+ const safeError = ensureError(error, 'HyperLiquidProvider.updateMargin');
9502
9019
  this.#deps.logger.error(safeError, this.#getErrorContext('updateMargin', {
9503
9020
  symbol: params.symbol,
9504
9021
  amount: params.amount,
@@ -10387,22 +9904,6 @@ export class HyperLiquidProvider {
10387
9904
  return [];
10388
9905
  }
10389
9906
  }
10390
- /**
10391
- * Return whether the active wallet can submit Hyperliquid actions.
10392
- *
10393
- * @returns The provider-owned account support result.
10394
- */
10395
- async getAccountSupport() {
10396
- await this.#ensureReady();
10397
- const context = {
10398
- lifecycleGeneration: this.#lifecycleGeneration,
10399
- network: this.#clientService.isTestnetMode() ? 'testnet' : 'mainnet',
10400
- userAddress: await this.#walletService.getUserAddressWithDefault(),
10401
- };
10402
- const support = await this.#getAccountSupportForContext(context);
10403
- await this.#assertAccountContextCurrent(context, 'Account support read');
10404
- return support;
10405
- }
10406
9907
  /**
10407
9908
  * Resolve the provider's currently active CAIP account identifier.
10408
9909
  * Used by the MarketDataService REST coalesce layer so cached payloads
@@ -11442,7 +10943,6 @@ export class HyperLiquidProvider {
11442
10943
  * @returns Result with txHash (HyperLiquid internal) and withdrawal ID
11443
10944
  */
11444
10945
  async withdraw(params) {
11445
- let accountContext;
11446
10946
  try {
11447
10947
  this.#deps.debugLogger.log('HyperLiquidProvider: STARTING WITHDRAWAL', {
11448
10948
  params,
@@ -11495,16 +10995,6 @@ export class HyperLiquidProvider {
11495
10995
  this.#deps.debugLogger.log('HyperLiquidProvider: ASSET SUPPORTED', {
11496
10996
  assetId: params.assetId,
11497
10997
  });
11498
- const lifecycleGeneration = this.#lifecycleGeneration;
11499
- const network = this.#clientService.isTestnetMode()
11500
- ? 'testnet'
11501
- : 'mainnet';
11502
- const userAddress = await this.#walletService.getUserAddressWithDefault();
11503
- accountContext = {
11504
- lifecycleGeneration,
11505
- network,
11506
- userAddress,
11507
- };
11508
10998
  // Step 3: Determine destination address
11509
10999
  this.#deps.debugLogger.log('HyperLiquidProvider: DETERMINING DESTINATION ADDRESS');
11510
11000
  let destination;
@@ -11515,7 +11005,7 @@ export class HyperLiquidProvider {
11515
11005
  });
11516
11006
  }
11517
11007
  else {
11518
- destination = userAddress;
11008
+ destination = await this.#walletService.getUserAddressWithDefault();
11519
11009
  this.#deps.debugLogger.log('HyperLiquidProvider: USING USER WALLET ADDRESS', {
11520
11010
  destination,
11521
11011
  });
@@ -11523,12 +11013,7 @@ export class HyperLiquidProvider {
11523
11013
  // Step 4: Ensure client is ready
11524
11014
  this.#deps.debugLogger.log('HyperLiquidProvider: ENSURING CLIENT READY');
11525
11015
  await this.#ensureReady();
11526
- accountContext = await this.#assertAccountSupported(accountContext);
11527
- await this.#ensureUnifiedAccountEnabled({
11528
- allowUserSigning: true,
11529
- accountContext,
11530
- });
11531
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Withdrawal');
11016
+ await this.#ensureUnifiedAccountEnabled({ allowUserSigning: true });
11532
11017
  const exchangeClient = this.#clientService.getExchangeClient();
11533
11018
  this.#deps.debugLogger.log('HyperLiquidProvider: CLIENT READY');
11534
11019
  // Step 5: Validate amount against account balance
@@ -11578,7 +11063,6 @@ export class HyperLiquidProvider {
11578
11063
  endpoint: 'withdraw3',
11579
11064
  timestamp: new Date().toISOString(),
11580
11065
  });
11581
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Withdrawal');
11582
11066
  const result = await exchangeClient.withdraw3({
11583
11067
  destination,
11584
11068
  amount: params.amount,
@@ -11605,11 +11089,7 @@ export class HyperLiquidProvider {
11605
11089
  // HyperLiquid's withdraw3 API doesn't return a transaction hash immediately
11606
11090
  };
11607
11091
  }
11608
- const rejection = new Error(`Withdrawal failed: ${JSON.stringify(result)}`);
11609
- const isMultiSigRequired = this.#recordUnsupportedAccountFromError(rejection, accountContext);
11610
- const errorMessage = isMultiSigRequired
11611
- ? PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED
11612
- : `Withdrawal failed: ${String(result.status)}`;
11092
+ const errorMessage = `Withdrawal failed: ${String(result.status)}`;
11613
11093
  this.#deps.debugLogger.log('HyperLiquidProvider: WITHDRAWAL FAILED', {
11614
11094
  error: errorMessage,
11615
11095
  status: result.status,
@@ -11622,18 +11102,13 @@ export class HyperLiquidProvider {
11622
11102
  };
11623
11103
  }
11624
11104
  catch (error) {
11625
- const isMultiSigRequired = accountContext
11626
- ? this.#recordUnsupportedAccountFromError(error, accountContext)
11627
- : false;
11628
11105
  const signerFailure = await this.#handleSignerFailure(error, 'withdraw', {
11629
11106
  assetId: params.assetId,
11630
11107
  });
11631
11108
  if (signerFailure) {
11632
11109
  return createErrorResult(signerFailure, { success: false });
11633
11110
  }
11634
- const safeError = isMultiSigRequired
11635
- ? new Error(PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED)
11636
- : ensureError(error, 'HyperLiquidProvider.initiateWithdrawal');
11111
+ const safeError = ensureError(error, 'HyperLiquidProvider.initiateWithdrawal');
11637
11112
  this.#deps.debugLogger.log('HyperLiquidProvider: WITHDRAWAL EXCEPTION', {
11638
11113
  error: safeError.message,
11639
11114
  errorType: safeError.name,
@@ -11649,7 +11124,7 @@ export class HyperLiquidProvider {
11649
11124
  operation: PERPS_ERROR_OPERATION.FinancialOperations,
11650
11125
  action: PERPS_ERROR_ACTION.FinancialWithdrawal,
11651
11126
  }));
11652
- return createErrorResult(safeError, { success: false });
11127
+ return createErrorResult(error, { success: false });
11653
11128
  }
11654
11129
  }
11655
11130
  /**
@@ -11672,17 +11147,6 @@ export class HyperLiquidProvider {
11672
11147
  * });
11673
11148
  */
11674
11149
  async transferBetweenDexs(params) {
11675
- return await this.#transferBetweenDexs(params);
11676
- }
11677
- /**
11678
- * Transfer collateral with an optional context captured by a parent action.
11679
- *
11680
- * @param params - Transfer parameters.
11681
- * @param expectedContext - Context captured by the parent action.
11682
- * @returns The transfer result.
11683
- */
11684
- async #transferBetweenDexs(params, expectedContext) {
11685
- let accountContext;
11686
11150
  try {
11687
11151
  this.#deps.debugLogger.log('HyperLiquidProvider: STARTING DEX TRANSFER', {
11688
11152
  params,
@@ -11695,13 +11159,14 @@ export class HyperLiquidProvider {
11695
11159
  if (params.sourceDex === params.destinationDex) {
11696
11160
  throw new Error('Source and destination DEX must be different');
11697
11161
  }
11698
- // Ensure client ready
11699
- await this.#ensureReady();
11700
- accountContext = await this.#assertAccountSupported(expectedContext);
11701
- const { userAddress } = accountContext;
11162
+ // Get user address
11163
+ const userAddress = await this.#walletService.getUserAddressWithDefault();
11702
11164
  this.#deps.debugLogger.log('HyperLiquidProvider: USER ADDRESS', {
11703
11165
  userAddress,
11704
11166
  });
11167
+ // Ensure client ready
11168
+ await this.#ensureReady();
11169
+ const exchangeClient = this.#clientService.getExchangeClient();
11705
11170
  // Execute transfer using SDK sendAsset()
11706
11171
  // Note: SDK docs say "testnet-only" but it works on mainnet (verified via Phantom)
11707
11172
  this.#deps.debugLogger.log('HyperLiquidProvider: CALLING SEND_ASSET API', {
@@ -11709,8 +11174,6 @@ export class HyperLiquidProvider {
11709
11174
  destinationDex: params.destinationDex || '(main)',
11710
11175
  amount: params.amount,
11711
11176
  });
11712
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'DEX transfer');
11713
- const exchangeClient = this.#clientService.getExchangeClient();
11714
11177
  const result = await exchangeClient.sendAsset({
11715
11178
  destination: userAddress,
11716
11179
  sourceDex: params.sourceDex,
@@ -11730,22 +11193,14 @@ export class HyperLiquidProvider {
11730
11193
  // User can verify transfer in explorer by timestamp
11731
11194
  };
11732
11195
  }
11733
- if (this.#recordUnsupportedAccountFromError(new Error(`DEX transfer failed: ${JSON.stringify(result)}`), accountContext)) {
11734
- throw new Error(PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED);
11735
- }
11736
11196
  throw new Error(PERPS_ERROR_CODES.TRANSFER_FAILED);
11737
11197
  }
11738
11198
  catch (error) {
11739
- const isMultiSigRequired = accountContext
11740
- ? this.#recordUnsupportedAccountFromError(error, accountContext)
11741
- : false;
11742
11199
  const signerFailure = await this.#handleSignerFailure(error, 'transferBetweenDexs', { sourceDex: params.sourceDex, destinationDex: params.destinationDex });
11743
11200
  if (signerFailure) {
11744
11201
  return { success: false, error: signerFailure.message };
11745
11202
  }
11746
- const safeError = isMultiSigRequired
11747
- ? new Error(PERPS_ERROR_CODES.EXCHANGE_MULTI_SIG_REQUIRED)
11748
- : ensureError(error, 'HyperLiquidProvider.transferToSpot');
11203
+ const safeError = ensureError(error, 'HyperLiquidProvider.transferToSpot');
11749
11204
  this.#deps.debugLogger.log('❌ HyperLiquidProvider: TRANSFER FAILED', {
11750
11205
  error: safeError.message,
11751
11206
  params,
@@ -11757,16 +11212,6 @@ export class HyperLiquidProvider {
11757
11212
  };
11758
11213
  }
11759
11214
  }
11760
- /**
11761
- * Transfer collateral using a context captured by a parent action.
11762
- *
11763
- * @param params - Transfer parameters.
11764
- * @param accountContext - Original action context for the transfer.
11765
- * @returns The transfer result.
11766
- */
11767
- async #transferBetweenDexsForContext(params, accountContext) {
11768
- return await this.#transferBetweenDexs(params, accountContext);
11769
- }
11770
11215
  /**
11771
11216
  * Subscribe to live price updates
11772
11217
  *
@@ -12049,11 +11494,6 @@ export class HyperLiquidProvider {
12049
11494
  try {
12050
11495
  const lifecycleGeneration = this.#lifecycleGeneration;
12051
11496
  const userAddress = await this.#walletService.getUserAddressWithDefault();
12052
- const preparationContext = {
12053
- lifecycleGeneration,
12054
- network: this.#clientService.isTestnetMode() ? 'testnet' : 'mainnet',
12055
- userAddress,
12056
- };
12057
11497
  // The result is only for the provider and account it started with.
12058
11498
  const assertPreparationCurrent = async () => {
12059
11499
  this.#assertProviderLifecycleCurrent(lifecycleGeneration, 'Trading wallet preparation');
@@ -12065,13 +11505,13 @@ export class HyperLiquidProvider {
12065
11505
  throw new Error(PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE);
12066
11506
  }
12067
11507
  };
12068
- const { accountContext } = await this.#ensureReadyForTrading({
11508
+ await this.#ensureReadyForTrading({
12069
11509
  requiresBuilderFee: false,
12070
11510
  recheckPendingReferral: true,
12071
- accountContext: preparationContext,
12072
11511
  });
12073
- await assertPreparationCurrent();
12074
- const { network } = accountContext;
11512
+ const network = this.#clientService.isTestnetMode()
11513
+ ? 'testnet'
11514
+ : 'mainnet';
12075
11515
  const isRegistered = await this.#isWalletOnHyperliquid(userAddress, network);
12076
11516
  await assertPreparationCurrent();
12077
11517
  // The venue rejects every write from a wallet with no HyperLiquid account
@@ -12087,7 +11527,7 @@ export class HyperLiquidProvider {
12087
11527
  await this.#ensureBuilderFeeSetup(undefined, {
12088
11528
  reportSignerFailure: true,
12089
11529
  reportRefusal: true,
12090
- }, accountContext);
11530
+ });
12091
11531
  // The builder fee setup ends quietly when the provider disconnects.
12092
11532
  await assertPreparationCurrent();
12093
11533
  if (!this.#walletService.isMainAccountSignerReady()) {
@@ -12731,7 +12171,6 @@ export class HyperLiquidProvider {
12731
12171
  // Clear session caches. Capability and fee reads use the lifecycle
12732
12172
  // generation above to discard any old response that resolves later.
12733
12173
  this.clearFeeCache();
12734
- this.#accountSupportByContext.clear();
12735
12174
  this.#referralCheckCache.clear();
12736
12175
  this.#builderFeeCheckCache.clear();
12737
12176
  this.#builderFeeRefusals.clear();
@@ -12977,26 +12416,20 @@ export class HyperLiquidProvider {
12977
12416
  *
12978
12417
  * Note: This is network-specific - testnet and mainnet have separate referral states
12979
12418
  * Note: Non-blocking - failures are logged to Sentry but don't prevent trading
12980
- *
12981
- * @param accountContext - Account/network/lifecycle captured by the action
12982
- * that is allowed to authorize a referral write.
12983
12419
  */
12984
- async #ensureReferralSet(accountContext) {
12420
+ async #ensureReferralSet() {
12985
12421
  this.#referralSetupNeedsRetry = false;
12986
12422
  this.#referralAwaitsBuilderCode = false;
12987
- const network = accountContext?.network ??
12988
- (this.#clientService.isTestnetMode() ? 'testnet' : 'mainnet');
12989
- const isTestnet = network === 'testnet';
12423
+ const isTestnet = this.#clientService.isTestnetMode();
12424
+ const network = isTestnet ? 'testnet' : 'mainnet';
12990
12425
  const expectedReferralCode = this.#getReferralCode(isTestnet);
12991
12426
  const referrerAddress = this.#getBuilderAddress(isTestnet);
12992
- let userAddress = accountContext?.userAddress;
12993
- if (!userAddress) {
12994
- try {
12995
- userAddress = await this.#walletService.getUserAddressWithDefault();
12996
- }
12997
- catch {
12998
- return; // Can't proceed without address
12999
- }
12427
+ let userAddress;
12428
+ try {
12429
+ userAddress = await this.#walletService.getUserAddressWithDefault();
12430
+ }
12431
+ catch {
12432
+ return; // Can't proceed without address
13000
12433
  }
13001
12434
  if (userAddress.toLowerCase() === referrerAddress.toLowerCase()) {
13002
12435
  this.#deps.debugLogger.log('[ensureReferralSet] User is builder, skipping', { network });
@@ -13038,9 +12471,6 @@ export class HyperLiquidProvider {
13038
12471
  return;
13039
12472
  }
13040
12473
  const codeStatus = await this.#getReferralCodeStatus();
13041
- if (accountContext) {
13042
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Referral code lookup');
13043
- }
13044
12474
  if (codeStatus !== 'ready') {
13045
12475
  this.#deps.debugLogger.log('[ensureReferralSet] Builder referral not ready, skipping', { network, codeStatus });
13046
12476
  // Don't cache. A failed lookup (already logged) waits for the next
@@ -13050,10 +12480,7 @@ export class HyperLiquidProvider {
13050
12480
  return;
13051
12481
  }
13052
12482
  // Check if user already has a referral on-chain
13053
- const hasReferral = await this.#checkReferralSet(userAddress);
13054
- if (accountContext) {
13055
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Referral account lookup');
13056
- }
12483
+ const hasReferral = await this.#checkReferralSet();
13057
12484
  if (hasReferral) {
13058
12485
  // Already has referral on-chain
13059
12486
  PerpsSigningCache.setReferral(network, userAddress, {
@@ -13064,7 +12491,7 @@ export class HyperLiquidProvider {
13064
12491
  }
13065
12492
  else {
13066
12493
  this.#deps.debugLogger.log('[ensureReferralSet] Setting referral (will show signing request)', { network, referralCode: expectedReferralCode });
13067
- const result = await this.#setReferralCode(accountContext);
12494
+ const result = await this.#setReferralCode();
13068
12495
  if (result) {
13069
12496
  this.#deps.debugLogger.log('[ensureReferralSet] Referral set successfully', { network });
13070
12497
  PerpsSigningCache.setReferral(network, userAddress, {
@@ -13092,11 +12519,6 @@ export class HyperLiquidProvider {
13092
12519
  completeInFlight();
13093
12520
  return;
13094
12521
  }
13095
- if (accountContext &&
13096
- this.#recordUnsupportedAccountFromError(error, accountContext)) {
13097
- completeInFlight();
13098
- return;
13099
- }
13100
12522
  // Safety net: wallet looked registered but the SDK still rejects with
13101
12523
  // "User or API Wallet 0x... does not exist." Do not forward to Sentry.
13102
12524
  // The walletRegistered cache stores positive observations only, so no
@@ -13106,12 +12528,6 @@ export class HyperLiquidProvider {
13106
12528
  completeInFlight();
13107
12529
  return;
13108
12530
  }
13109
- if (accountContext &&
13110
- ensureError(error, 'HyperLiquidProvider.ensureReferralSet').message ===
13111
- PERPS_ERROR_CODES.PROVIDER_LIFECYCLE_STALE) {
13112
- completeInFlight();
13113
- throw error;
13114
- }
13115
12531
  // Cache failure to prevent retries
13116
12532
  PerpsSigningCache.setReferral(network, userAddress, {
13117
12533
  attempted: true,
@@ -13169,20 +12585,18 @@ export class HyperLiquidProvider {
13169
12585
  /**
13170
12586
  * Check if user has a referral code set with HyperLiquid
13171
12587
  *
13172
- * @param userAddress - Account to inspect, or the currently selected account
13173
- * when omitted.
13174
12588
  * @returns Promise resolving to true if referral is set, false otherwise
13175
12589
  */
13176
- async #checkReferralSet(userAddress) {
12590
+ async #checkReferralSet() {
13177
12591
  try {
13178
12592
  const infoClient = this.#clientService.getInfoClient();
13179
- const referralUserAddress = userAddress ?? (await this.#walletService.getUserAddressWithDefault());
12593
+ const userAddress = await this.#walletService.getUserAddressWithDefault();
13180
12594
  // Call HyperLiquid API to check if user has a referral set
13181
12595
  const referralData = await infoClient.referral({
13182
- user: referralUserAddress,
12596
+ user: userAddress,
13183
12597
  });
13184
12598
  this.#deps.debugLogger.log('Referral check result:', {
13185
- userAddress: referralUserAddress,
12599
+ userAddress,
13186
12600
  referralData,
13187
12601
  });
13188
12602
  return Boolean(referralData?.referredBy?.code);
@@ -13206,15 +12620,10 @@ export class HyperLiquidProvider {
13206
12620
  /**
13207
12621
  * Set MetaMask as the user's referrer on HyperLiquid
13208
12622
  *
13209
- * @param accountContext - Account/network/lifecycle captured by the action
13210
- * that is allowed to authorize this write.
13211
12623
  * @returns A promise that resolves to the boolean result.
13212
12624
  */
13213
- async #setReferralCode(accountContext) {
12625
+ async #setReferralCode() {
13214
12626
  try {
13215
- if (accountContext) {
13216
- await this.#assertNoKnownUnsupportedAccount(accountContext, 'Referral setup');
13217
- }
13218
12627
  const exchangeClient = this.#clientService.getExchangeClient();
13219
12628
  const referralCode = this.#getReferralCode(this.#clientService.isTestnetMode());
13220
12629
  this.#deps.debugLogger.log('[setReferralCode] Setting referral code', {
@@ -13226,18 +12635,9 @@ export class HyperLiquidProvider {
13226
12635
  code: referralCode,
13227
12636
  });
13228
12637
  this.#deps.debugLogger.log('[setReferralCode] Referral code set result', result);
13229
- if (result?.status !== 'ok' &&
13230
- accountContext &&
13231
- this.#recordUnsupportedAccountFromError(new Error(`Referral setup failed: ${JSON.stringify(result)}`), accountContext)) {
13232
- return false;
13233
- }
13234
12638
  return result?.status === 'ok';
13235
12639
  }
13236
12640
  catch (error) {
13237
- if (accountContext &&
13238
- this.#recordUnsupportedAccountFromError(error, accountContext)) {
13239
- throw error;
13240
- }
13241
12641
  // Retryable (a locked keyring, an unavailable agent, or one the venue
13242
12642
  // rejected): `#ensureReferralSet` retries at the next entry, so it is
13243
12643
  // not an error to report.