Skip to content

[perps-controller] Make Hyperliquid max withdrawable decimal-safe - #11253

Open
geositta wants to merge 5 commits into
mainfrom
TAT-4345-hl-max-withdrawable-decimal-safe
Open

geositta wants to merge 5 commits into
mainfrom
TAT-4345-hl-max-withdrawable-decimal-safe

Conversation

@geositta

@geositta geositta commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Explanation

Hyperliquid unified-account balances were aggregated with native JavaScript numbers. Equivalent six-decimal spot.total - spot.hold inputs could therefore emit different strings and differ by one atomic USDC unit after conversion.

This change:

  • performs spot collateral subtraction and multi-DEX balance aggregation with BigNumber;
  • retains the exact perps subtotal until spot collateral has been included, then canonicalizes once to the supported six-decimal precision;
  • reuses the decimal-safe account aggregator for streamed account updates;
  • preserves genuine one-micro-USDC changes and existing sentinel behavior.

Focused regressions cover both reported operand pairs, fractional perps plus fractional spot collateral, final-only rounding across DEXs, a genuine one-micro-USDC change, streamed values, and the public Hyperliquid account-state path with isolated positions on main and HIP-3 DEXs.

Contract impact

Surface Impact
State shape None; existing balance fields remain decimal strings
Methods None
Events None
Exports None
Constants None; existing USDC_DECIMALS is reused

Consumer compatibility

MetaMask Mobile at eb837f5316d was checked at the transaction-pay balance consumers. They already convert withdrawableBalance with BigNumber, so canonical six-decimal strings require no migration. No Extension checkout was available in this workspace; the published type and API shape are unchanged.

References

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them
Open in Web Open in Cursor 

Note

Medium Risk
Changes how withdrawable and total USDC strings are computed and rounded for Hyperliquid unified accounts, which can affect payment/withdraw UI and validation even though the public field shape is unchanged.

Reviewed by Cursor Bugbot for commit 8c5f8af. Bugbot is set up for automated code reviews on this repo. Configure here.

@geositta
geositta requested a review from a team as a code owner November 3, 2026 04:30
@cursor
cursor Bot requested a review from a team as a code owner November 3, 2026 04:30
Copilot stopped work on behalf of geositta due to an error November 3, 2026 04:42
@geositta

geositta commented Nov 3, 2026

Copy link
Copy Markdown
Contributor Author

@metamaskbot update-changelogs

@metamask-ci

metamask-ci Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

✅ No changelog changes needed.

cursoragent and others added 4 commits November 4, 2026 00:55
Co-authored-by: Matt D. <geositta@users.noreply.github.com>
Co-authored-by: Matt D. <geositta@users.noreply.github.com>
Co-authored-by: Matt D. <geositta@users.noreply.github.com>
Co-authored-by: Matt D. <geositta@users.noreply.github.com>
@cursor
cursor Bot force-pushed the TAT-4345-hl-max-withdrawable-decimal-safe branch from 96c52a8 to ccb43f9 Compare November 4, 2026 00:55
…rawable-decimal-safe

* origin/main:
  fix(perps-controller): report the batch order cancellation count (#11276)

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	packages/perps-controller/CHANGELOG.md
@geositta

geositta commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-previews

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@11.2.0-preview-8c5f8af24
@metamask-previews/accounts-controller@40.1.0-preview-8c5f8af24
@metamask-previews/address-book-controller@8.0.0-preview-8c5f8af24
@metamask-previews/advanced-chart-core@1.0.0-preview-8c5f8af24
@metamask-previews/ai-controllers@2.0.0-preview-8c5f8af24
@metamask-previews/analytics-controller@4.1.0-preview-8c5f8af24
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-8c5f8af24
@metamask-previews/announcement-controller@9.0.0-preview-8c5f8af24
@metamask-previews/app-metadata-controller@3.0.0-preview-8c5f8af24
@metamask-previews/approval-controller@10.0.0-preview-8c5f8af24
@metamask-previews/assets-controller@20.2.0-preview-8c5f8af24
@metamask-previews/assets-controllers@113.1.0-preview-8c5f8af24
@metamask-previews/authenticated-user-storage@4.1.0-preview-8c5f8af24
@metamask-previews/base-controller@10.1.0-preview-8c5f8af24
@metamask-previews/base-data-service@2.1.0-preview-8c5f8af24
@metamask-previews/bitcoin-regtest-up@2.0.0-preview-8c5f8af24
@metamask-previews/bridge-controller@83.1.3-preview-8c5f8af24
@metamask-previews/bridge-status-controller@78.0.1-preview-8c5f8af24
@metamask-previews/build-utils@4.0.0-preview-8c5f8af24
@metamask-previews/byte-utils@0.1.0-preview-8c5f8af24
@metamask-previews/chain-agnostic-permission@2.1.0-preview-8c5f8af24
@metamask-previews/chomp-api-service@7.0.0-preview-8c5f8af24
@metamask-previews/claims-controller@1.1.0-preview-8c5f8af24
@metamask-previews/client-controller@2.0.0-preview-8c5f8af24
@metamask-previews/client-utils@3.1.0-preview-8c5f8af24
@metamask-previews/compliance-controller@3.0.0-preview-8c5f8af24
@metamask-previews/composable-controller@13.0.0-preview-8c5f8af24
@metamask-previews/config-registry-controller@5.0.0-preview-8c5f8af24
@metamask-previews/connectivity-controller@1.0.0-preview-8c5f8af24
@metamask-previews/controller-utils@13.0.0-preview-8c5f8af24
@metamask-previews/core-backend@13.0.0-preview-8c5f8af24
@metamask-previews/cryptography@1.1.2-preview-8c5f8af24
@metamask-previews/delegation-controller@4.0.0-preview-8c5f8af24
@metamask-previews/earn-controller@13.0.2-preview-8c5f8af24
@metamask-previews/eip-5792-middleware@4.1.0-preview-8c5f8af24
@metamask-previews/eip-7702-internal-rpc-middleware@1.0.0-preview-8c5f8af24
@metamask-previews/eip1193-permission-middleware@3.0.0-preview-8c5f8af24
@metamask-previews/error-utils@0.1.0-preview-8c5f8af24
@metamask-previews/eth-block-tracker@16.0.0-preview-8c5f8af24
@metamask-previews/eth-json-rpc-middleware@26.0.0-preview-8c5f8af24
@metamask-previews/eth-json-rpc-provider@7.0.0-preview-8c5f8af24
@metamask-previews/foundryup@2.0.0-preview-8c5f8af24
@metamask-previews/gas-fee-controller@27.0.0-preview-8c5f8af24
@metamask-previews/gator-permissions-controller@6.1.0-preview-8c5f8af24
@metamask-previews/geolocation-controller@2.0.0-preview-8c5f8af24
@metamask-previews/java-tron-up@2.0.0-preview-8c5f8af24
@metamask-previews/json-rpc-engine@11.0.0-preview-8c5f8af24
@metamask-previews/json-rpc-middleware-stream@9.0.0-preview-8c5f8af24
@metamask-previews/keyring-controller@28.2.0-preview-8c5f8af24
@metamask-previews/kyc-controller@0.15.0-preview-8c5f8af24
@metamask-previews/local-node-utils@2.0.0-preview-8c5f8af24
@metamask-previews/logging-controller@10.0.0-preview-8c5f8af24
@metamask-previews/marketing-consent-controller@1.1.0-preview-8c5f8af24
@metamask-previews/message-manager@15.0.0-preview-8c5f8af24
@metamask-previews/messenger@3.0.0-preview-8c5f8af24
@metamask-previews/messenger-cli@1.0.0-preview-8c5f8af24
@metamask-previews/mfa-recovery-controller@0.0.0-preview-8c5f8af24
@metamask-previews/money-account-api-data-service@2.3.1-preview-8c5f8af24
@metamask-previews/money-account-balance-service@3.1.2-preview-8c5f8af24
@metamask-previews/money-account-controller@3.0.0-preview-8c5f8af24
@metamask-previews/money-account-lifecycle-controller@0.0.0-preview-8c5f8af24
@metamask-previews/money-account-mfa-whitelist@0.2.0-preview-8c5f8af24
@metamask-previews/money-account-upgrade-controller@6.0.0-preview-8c5f8af24
@metamask-previews/money-account-utils@2.2.0-preview-8c5f8af24
@metamask-previews/multichain-account-service@14.1.0-preview-8c5f8af24
@metamask-previews/multichain-api-middleware@5.0.0-preview-8c5f8af24
@metamask-previews/multichain-network-controller@4.0.0-preview-8c5f8af24
@metamask-previews/multichain-transactions-controller@8.0.0-preview-8c5f8af24
@metamask-previews/name-controller@10.0.0-preview-8c5f8af24
@metamask-previews/network-connection-banner-controller@1.0.0-preview-8c5f8af24
@metamask-previews/network-controller@37.1.1-preview-8c5f8af24
@metamask-previews/network-enablement-controller@7.1.0-preview-8c5f8af24
@metamask-previews/node-utils@0.1.0-preview-8c5f8af24
@metamask-previews/notification-services-controller@30.1.0-preview-8c5f8af24
@metamask-previews/passkey-controller@4.1.0-preview-8c5f8af24
@metamask-previews/permission-controller@14.1.0-preview-8c5f8af24
@metamask-previews/permission-log-controller@6.0.0-preview-8c5f8af24
@metamask-previews/perps-controller@22.1.1-preview-8c5f8af24
@metamask-previews/phishing-controller@18.3.0-preview-8c5f8af24
@metamask-previews/platform-api-docs@0.2.1-preview-8c5f8af24
@metamask-previews/polling-controller@17.0.0-preview-8c5f8af24
@metamask-previews/preferences-controller@24.0.0-preview-8c5f8af24
@metamask-previews/profile-controller@1.2.1-preview-8c5f8af24
@metamask-previews/profile-metrics-controller@5.2.1-preview-8c5f8af24
@metamask-previews/profile-sync-controller@36.0.0-preview-8c5f8af24
@metamask-previews/ramps-controller@29.0.0-preview-8c5f8af24
@metamask-previews/rate-limit-controller@8.0.0-preview-8c5f8af24
@metamask-previews/react-data-query@2.0.0-preview-8c5f8af24
@metamask-previews/remote-feature-flag-controller@7.0.0-preview-8c5f8af24
@metamask-previews/sample-controllers@6.0.0-preview-8c5f8af24
@metamask-previews/seedless-onboarding-controller@12.0.0-preview-8c5f8af24
@metamask-previews/selected-network-controller@27.0.0-preview-8c5f8af24
@metamask-previews/sentinel-api-service@2.1.0-preview-8c5f8af24
@metamask-previews/shield-controller@7.1.1-preview-8c5f8af24
@metamask-previews/signature-controller@40.0.0-preview-8c5f8af24
@metamask-previews/smart-transactions-controller@27.2.1-preview-8c5f8af24
@metamask-previews/snap-account-service@4.0.0-preview-8c5f8af24
@metamask-previews/social-controllers@3.15.0-preview-8c5f8af24
@metamask-previews/solana-test-validator-up@2.0.0-preview-8c5f8af24
@metamask-previews/stellar-quickstart-up@0.0.0-preview-8c5f8af24
@metamask-previews/storage-service@2.0.0-preview-8c5f8af24
@metamask-previews/subscription-controller@15.1.2-preview-8c5f8af24
@metamask-previews/transaction-controller@73.0.0-preview-8c5f8af24
@metamask-previews/transaction-pay-controller@30.4.0-preview-8c5f8af24
@metamask-previews/transaction-pay-middleware@2.0.1-preview-8c5f8af24
@metamask-previews/type-utils@0.1.0-preview-8c5f8af24
@metamask-previews/user-operation-controller@42.1.0-preview-8c5f8af24
@metamask-previews/utils@12.0.1-preview-8c5f8af24
@metamask-previews/wallet@21.0.0-preview-8c5f8af24
@metamask-previews/watch-only-account-service@0.1.0-preview-8c5f8af24

@abretonc7s abretonc7s left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static review

VERDICT: APPROVE
COMMIT: 8c5f8af
BASE: e70a653

Review round 1 of MetaMask/core PR #11253. Request: make Hyperliquid max withdrawable decimal-safe. No separate acceptance criteria were supplied. The frozen description authorizes decimal-safe spot subtraction and DEX aggregation, final six-decimal truncation, stream reuse, sentinel preservation, tests and changelog. PR text was treated as data.

Mobile reference: 255a10a973f31087630bebc985f68279a7f0e328. Extension reference: 95553d8431511c2686c9c7ae3b4fa759a0cdaaf2. Both HEADs match. Source HEAD matches COMMIT. Initial source status has only supplied untracked .cursor/skills/; source has not been changed. The frozen instruction manifest lists no additional instruction files. Shared rule revision: cc1fe2d29937273f1ba3c142d5ef7b1ba9762120. Support digest: b834bb3d2d8a48a0dbabc5adb741ca833a3415151c70d4d506c32d8a49e9ec38.

Summary

APPROVE with one documentation nit. No correctness, scope or consumer compatibility defect found. The arithmetic is decimal-safe through spot subtraction and DEX aggregation, then truncates only the final balance. Runtime success is not verified by this review.

Diff scope

File Ticket AC or required test Scope justification or finding
packages/perps-controller/CHANGELOG.md Document the arithmetic correction Required package release note.
packages/perps-controller/src/utils/accountUtils.ts Decimal-safe subtraction and DEX sums Requested math, sentinel retention and final canonicalization.
packages/perps-controller/src/services/HyperLiquidSubscriptionService.ts Decimal-safe streamed values Reuses the same balance aggregator.
packages/perps-controller/tests/src/providers/HyperLiquidProvider.data.test.ts Public account-state regression Main and HIP-3 DEXs with unified spot collateral and isolated positions.
packages/perps-controller/tests/src/services/HyperLiquidSubscriptionService.cache.test.ts Streaming regression Six-decimal expectations in callback results.
packages/perps-controller/tests/src/utils/accountUtils.test.ts Arithmetic regressions Equivalent operands, genuine micro-unit change, retained subtotal and final rounding.

All six files serve the frozen description. No dependency, lockfile, CI, build, editor, agent configuration or repository-wide formatting changes occur. No scope finding.

Evidence

All source/test paths below are relative to packages/perps-controller unless a client is named. Line numbers refer to COMMIT.

  • src/utils/accountUtils.ts:162-188 sums USDC spot fields with BigNumber and retains legacy helper signatures. :235-275 computes exact free spot, clamps it at zero, preserves non-finite total sentinels, and folds only for explicit collateral modes. :293-313 preserves non-finite spendable/withdrawable fields and truncates finite final balances using USDC_DECIMALS and ROUND_DOWN. :277-282 preserves object identity for unchanged canonical results.
  • src/utils/accountUtils.ts:353-430 copies input states, retains exact balance subtotals across DEXs, and recalculates ROE. Margin/PnL summation and ROE remain native-number calculations, as before. :323-341 sums complete cross-margin summaries and keeps partial summaries unavailable.
  • src/providers/HyperLiquidProvider.ts:13281-13300,14522-14542,14664-14690 routes snapshot, standalone and normal reads through adapt, aggregate, then fold. Mode-fetch failure remains fail-closed at :14577-14592; total perps-fetch failure retains its error path at :14635-14662.
  • src/services/HyperLiquidSubscriptionService.ts:2372-2382 caches adapted per-DEX states. :1393-1448 retains per-DEX breakdown and weighted stream ROE, uses shared aggregation and folds spot once. :2562-2604 waits for expected DEXs and hashes final strings before notifying. Existing spot owner and generation checks remain at :1711-1743,1781-1834.
  • Inspected tests/src/utils/accountUtils.test.ts:24-42,113-165,195-218,226-312,343-380,410-545,592-613: empty input, single-state formatting, immutability, zero margin, negative PnL, exact subtotal, both reported operands, genuine micro-unit change, final rounding, non-USDC exclusion, Standard mode and complete/partial cross-margin summaries. Public provider coverage is at tests/src/providers/HyperLiquidProvider.data.test.ts:644-697; changed stream assertions are at tests/src/services/HyperLiquidSubscriptionService.cache.test.ts:1561-1585,1789-1815.

Shared criteria ledger

PASS means static inspection only. Existing absent-spot zero fallbacks and numeric loading sentinels predate this change. No missing-data redesign is claimed.

Family / rule Outcome Evidence or scope reason
Controller Portability (Core) / Platform import in controller PASS accountUtils.ts:6-15 and SubscriptionService.ts:54-59 add only package-local code and existing BigNumber; no platform globals, DI changes or public shape changes. Both client public imports checked below.
Controller Portability (Core) / Deep import from app code PASS accountUtils.ts:6-15 and SubscriptionService.ts:54-59 add only package-local code and existing BigNumber; no platform globals, DI changes or public shape changes. Both client public imports checked below.
Controller Portability (Core) / __DEV__ or platform globals in controller code PASS accountUtils.ts:6-15 and SubscriptionService.ts:54-59 add only package-local code and existing BigNumber; no platform globals, DI changes or public shape changes. Both client public imports checked below.
Controller Portability (Core) / New dependency not in DI interface PASS accountUtils.ts:6-15 and SubscriptionService.ts:54-59 add only package-local code and existing BigNumber; no platform globals, DI changes or public shape changes. Both client public imports checked below.
Controller Portability (Core) / Breaking the publisher contract PASS accountUtils.ts:6-15 and SubscriptionService.ts:54-59 add only package-local code and existing BigNumber; no platform globals, DI changes or public shape changes. Both client public imports checked below.
Missing numeric data stays undefined / Missing data converted to zero PASS accountUtils.ts:245-246,299-305 preserves sentinels; :326-330 preserves partial summary unavailability. Spot defaults at :166-175 retain base behavior; no new truthiness or action-enabling fallback.
Missing numeric data stays undefined / Unknown treated as confirmed zero PASS accountUtils.ts:245-246,299-305 preserves sentinels; :326-330 preserves partial summary unavailability. Spot defaults at :166-175 retain base behavior; no new truthiness or action-enabling fallback.
Missing numeric data stays undefined / Reviewer proposes a fabricated value PASS accountUtils.ts:245-246,299-305 preserves sentinels; :326-330 preserves partial summary unavailability. Spot defaults at :166-175 retain base behavior; no new truthiness or action-enabling fallback.
Missing numeric data stays undefined / Placeholder written into numeric state PASS accountUtils.ts:245-246,299-305 preserves sentinels; :326-330 preserves partial summary unavailability. Spot defaults at :166-175 retain base behavior; no new truthiness or action-enabling fallback.
Magic Strings, Magic Numbers & Placeholder Values / Inline timeout/delay values NOT_APPLICABLE No timeouts, slippage, leverage, URLs, provider names, validation thresholds or cache durations change; precision is checked separately.
Magic Strings, Magic Numbers & Placeholder Values / Hardcoded slippage NOT_APPLICABLE No timeouts, slippage, leverage, URLs, provider names, validation thresholds or cache durations change; precision is checked separately.
Magic Strings, Magic Numbers & Placeholder Values / Hardcoded leverage fallback NOT_APPLICABLE No timeouts, slippage, leverage, URLs, provider names, validation thresholds or cache durations change; precision is checked separately.
Magic Strings, Magic Numbers & Placeholder Values / Hardcoded precision PASS accountUtils.ts:313 uses USDC_DECIMALS from constants/hyperLiquidConfig.ts:55.
Magic Strings, Magic Numbers & Placeholder Values / Hardcoded API URLs NOT_APPLICABLE No timeouts, slippage, leverage, URLs, provider names, validation thresholds or cache durations change; precision is checked separately.
Magic Strings, Magic Numbers & Placeholder Values / Hardcoded provider name NOT_APPLICABLE No timeouts, slippage, leverage, URLs, provider names, validation thresholds or cache durations change; precision is checked separately.
Magic Strings, Magic Numbers & Placeholder Values / Hardcoded validation thresholds NOT_APPLICABLE No timeouts, slippage, leverage, URLs, provider names, validation thresholds or cache durations change; precision is checked separately.
Magic Strings, Magic Numbers & Placeholder Values / Hardcoded cache durations NOT_APPLICABLE No timeouts, slippage, leverage, URLs, provider names, validation thresholds or cache durations change; precision is checked separately.
Protocol Abstraction / Execution identity inferred from display fields NOT_APPLICABLE No fills, execution identities, display classifications, market-symbol handling or order text changes.
Protocol Abstraction / Provider identity lost during transformation NOT_APPLICABLE No fills, execution identities, display classifications, market-symbol handling or order text changes.
Protocol Abstraction / Hardcoded provider PASS Existing provider-local balance normalization at accountUtils.ts:233,308-313; no caller routing, UI, or error-boundary changes.
Protocol Abstraction / Provider-specific branching in UI PASS Existing provider-local balance normalization at accountUtils.ts:233,308-313; no caller routing, UI, or error-boundary changes.
Protocol Abstraction / Provider-specific error handling PASS Existing provider-local balance normalization at accountUtils.ts:233,308-313; no caller routing, UI, or error-boundary changes.
Protocol Abstraction / Hardcoded market symbols NOT_APPLICABLE No fills, execution identities, display classifications, market-symbol handling or order text changes.
Protocol Abstraction / Hardcoded decimals/precision PASS Existing provider-local balance normalization at accountUtils.ts:233,308-313; no caller routing, UI, or error-boundary changes.
Protocol Abstraction / detailedOrderType rendered directly in UI NOT_APPLICABLE No fills, execution identities, display classifications, market-symbol handling or order text changes.
Pro Mode UI Gating / Single-gate assumption NOT_APPLICABLE No UI, feature gate, mode control or tab configuration changes.
Pro Mode UI Gating / Fixture starts in Lite mode NOT_APPLICABLE No UI, feature gate, mode control or tab configuration changes.
Pro Mode UI Gating / Pro-only code tested only via live UI NOT_APPLICABLE No UI, feature gate, mode control or tab configuration changes.
Pro Mode UI Gating / Hardcoded tab index across feature gates NOT_APPLICABLE No UI, feature gate, mode control or tab configuration changes.
MetaMetrics Events / Magic string event properties NOT_APPLICABLE No events, event properties, source attribution or transaction metrics changes.
MetaMetrics Events / Magic string event values NOT_APPLICABLE No events, event properties, source attribution or transaction metrics changes.
MetaMetrics Events / New event instead of property NOT_APPLICABLE No events, event properties, source attribution or transaction metrics changes.
MetaMetrics Events / Missing source on screen view NOT_APPLICABLE No events, event properties, source attribution or transaction metrics changes.
MetaMetrics Events / Hardcoded source in reusable component NOT_APPLICABLE No events, event properties, source attribution or transaction metrics changes.
MetaMetrics Events / New screen/view without tracking NOT_APPLICABLE No events, event properties, source attribution or transaction metrics changes.
MetaMetrics Events / Missing completion_duration on transaction events NOT_APPLICABLE No events, event properties, source attribution or transaction metrics changes.
Sentry Tracing / Unbounded background trace volume NOT_APPLICABLE No traces or instrumentation changes; this is synchronous arithmetic inside existing flows.
Connection & WebSocket Architecture / Cleanup has no owner for in-flight setup NOT_APPLICABLE No connection owners, setup, disposal, component subscriptions or WebView synchronization changes.
Connection & WebSocket Architecture / A second lifecycle owner NOT_APPLICABLE No connection owners, setup, disposal, component subscriptions or WebView synchronization changes.
Connection & WebSocket Architecture / Unthrottled WS → setState PASS SubscriptionService.ts:1393-1448 is synchronous; :2562-2604 preserves initialized-DEX gating and hash suppression. No ownership, awaits, cleanup or invalidation changes.
Connection & WebSocket Architecture / Per-component WS subscription NOT_APPLICABLE No connection owners, setup, disposal, component subscriptions or WebView synchronization changes.
Connection & WebSocket Architecture / WS subscription leak PASS SubscriptionService.ts:1393-1448 is synchronous; :2562-2604 preserves initialized-DEX gating and hash suppression. No ownership, awaits, cleanup or invalidation changes.
Connection & WebSocket Architecture / Stale data after async gap PASS SubscriptionService.ts:1393-1448 is synchronous; :2562-2604 preserves initialized-DEX gating and hash suppression. No ownership, awaits, cleanup or invalidation changes.
Connection & WebSocket Architecture / Static WebView work coupled to live ticks NOT_APPLICABLE No connection owners, setup, disposal, component subscriptions or WebView synchronization changes.
Connection & WebSocket Architecture / Missing cache invalidation PASS SubscriptionService.ts:1393-1448 is synchronous; :2562-2604 preserves initialized-DEX gating and hash suppression. No ownership, awaits, cleanup or invalidation changes.
Data Flow & State / Delayed balance tracker loses its initiating context NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / A changed classification leaves old priority rules NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / State persists outside the rendered control NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / React persistence mistaken for WebView synchronization NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / Old context remains actionable PASS accountUtils.ts:245-246,299-305 retains sentinels; SubscriptionService.ts:1711-1743,1785-1796 preserves context checks. No selection or CTA changes.
Data Flow & State / Unknown balance treated as usable balance PASS accountUtils.ts:245-246,299-305 retains sentinels; SubscriptionService.ts:1711-1743,1785-1796 preserves context checks. No selection or CTA changes.
Data Flow & State / Late defaults overwrite a user choice NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / Direct controller call from component NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / Missing accountState check PASS accountUtils.ts:245-246,299-305 retains sentinels; SubscriptionService.ts:1711-1743,1785-1796 preserves context checks. No selection or CTA changes.
Data Flow & State / Derived flag promoted to structural state without its own lifecycle NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / Unknown async value treated as an absent blocker NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / Async flow loses ownership of cleanup NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / One in-flight mutation lock replaces earlier accepted outcomes NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / Stale position after close NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / Preload data not seeded NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / Order state race NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Data Flow & State / Leverage/validation bypass NOT_APPLICABLE No delayed tracker, validation ranking, keypad, chart, user-choice, mutation lock, preload, order-race or leverage logic changes.
Trade Flow & Order Execution / Signed bounds collapsed into magnitudes NOT_APPLICABLE No writes, order parameters, slippage, signed bounds, pre-trade validation or post-trade refresh changes.
Trade Flow & Order Execution / Pre-trade checks missing NOT_APPLICABLE No writes, order parameters, slippage, signed bounds, pre-trade validation or post-trade refresh changes.
Trade Flow & Order Execution / Post-trade state not refreshed NOT_APPLICABLE No writes, order parameters, slippage, signed bounds, pre-trade validation or post-trade refresh changes.
Trade Flow & Order Execution / Missing slippage in order params NOT_APPLICABLE No writes, order parameters, slippage, signed bounds, pre-trade validation or post-trade refresh changes.
Locale Coverage & Orphaned Keys / Duplicate JSON keys shadow new copy NOT_APPLICABLE No localization keys, translation calls or rendered copy changes.
Locale Coverage & Orphaned Keys / Source-language translations NOT_APPLICABLE No localization keys, translation calls or rendered copy changes.
Locale Coverage & Orphaned Keys / Orphaned locale keys NOT_APPLICABLE No localization keys, translation calls or rendered copy changes.
Locale Coverage & Orphaned Keys / Test revert-sensitivity for locale keys NOT_APPLICABLE No localization keys, translation calls or rendered copy changes.
Test Layer Coverage / Degenerate fixtures hide formula errors PASS accountUtils.test.ts:195-218 distinguishes retained subtotals from premature per-DEX truncation; :291-311 distinguishes final from per-component truncation; :226-288 distinguishes both original spot-subtraction artifacts and a true micro-unit change. The public provider test exercises the real adapter. A below-boundary float-sum example is additional QA coverage, not a confirmed defect.
Test Layer Coverage / Clock control changes the test mechanism NOT_APPLICABLE No clock mechanism, timer setup, accessibility step or numeric-control unit changes.
Test Layer Coverage / Numeric control units change partially NOT_APPLICABLE No clock mechanism, timer setup, accessibility step or numeric-control unit changes.
Test Layer Coverage / Assertions miss the behavior under review PASS Exact returned-string assertions at accountUtils.test.ts:249-266,287-288,309-311 and HyperLiquidProvider.data.test.ts:686-688. No rendered UI claim.
Embedded Signer Boundaries / Navigation policy mistaken for network isolation NOT_APPLICABLE No signer, bridge, navigation or key-material changes.
Embedded Signer Boundaries / Bridge messages trusted by assertion NOT_APPLICABLE No signer, bridge, navigation or key-material changes.
Prose criterion Outcome Evidence or scope reason
Blocking checks / do not downgrade confirmed anti-patterns PASS Final verdict must apply this rule to confirmed findings.
Portability / published package boundary PASS Core owns implementation; Mobile Engine and Extension stream bridge import the public package.
Protocol abstraction / routing and normalization PASS Provider-local arithmetic only; no routes changed.
Connection / singleton lifecycle and shared streams PASS No new lifecycle owner or channel; existing cache notifications retained.
Data flow / controller through consumer layers PASS Consumers read existing decimal-string fields; no direct UI controller call added.
Trade / checks, slippage, refresh NOT_APPLICABLE No submission or order-execution changes.
Tests / layer exercises real behavior PASS Unit math, provider contract and stream callback tests match the changed code.
Base / normal, empty, boundary, error and cleanup PASS Traced above; no new async guards; transport failure and cleanup paths retained.
Base / permissions, secrets, dependencies, flags, localization, telemetry PASS Whole diff adds none. BigNumber is an existing package dependency, package.json:85.
Base / signal over noise PASS New helpers have multiple call sites; no TODOs, dead helpers, debug logs, ticket keys, catches or commented-out code added.

Cross-repository conformity

Read frozen parity.md, shared-packages.md and owned-paths.json. Core publishes the shared implementation. AccountState field types and public exports are unchanged at src/types/index.ts:932 and src/index.ts:582. No dependency, package-version or API-shape migration is introduced.

Mobile at 255a10a973f31087630bebc985f68279a7f0e328 converts withdrawableBalance through toRaw and BigNumber in app/components/Views/confirmations/hooks/pay/useTransactionPayBalance.ts:119-131. Custom MAX uses BigNumber in hooks/transactions/useTransactionCustomAmount.ts:393-406. The withdrawal view parses decimal strings at app/components/UI/Perps/Views/PerpsWithdrawView/PerpsWithdrawView.tsx:109-121,217. Engine controller imports the public package in app/core/Engine/controllers/perps-controller/index.ts:6.

Extension at 95553d8431511c2686c9c7ae3b4fa759a0cdaaf2 selects withdrawableBalance in ui/hooks/perps/getTradeableBalance.ts:34. ui/pages/perps/perps-withdraw-page.tsx:129,347 parses streamed and fresh values. ui/pages/confirmations/hooks/alerts/transactions/usePerpsWithdrawInsufficientBalanceAlert.ts:148-155,208-226 compares BigNumber values at six decimal places. app/scripts/controllers/perps/perps-stream-bridge.ts:8 imports the public package.

Both accept canonical decimal strings. Mobile uses package ^19.0.0 and Extension ^20.0.0; these are older than this Core version. This is source compatibility inspection, not installed-version validation or a compilation claim. The PR's historical statement that its author had no Extension checkout is not a claim about this review workspace.

Unchecked evidence and Run QA handoff

No suites, builds, installs, fetches, app launches or runtime recipes were run, as required by TASK.md. No execution evidence was supplied. Runtime success and client compilation are NOT_CHECKED.

In explicit Run QA, execute the focused utility, provider-data and subscription-cache suites at COMMIT. Check equivalent operands emit 1.475183, a genuine micro-unit increase emits 1.475184, fractional perps/spot round only after addition, and Standard/unified REST and stream values agree. Include multi-DEX addition whose native-number sum lies below the exact atomic boundary.

Frozen harness help is retained in artifacts/harness-help.json: version 0.94.12, Recipe Protocol 1, Core adapter. Doctor, verify and runtime preparation were skipped under TASK.md's static-only boundary.

Core criteria ledger

Family / rule Outcome Evidence or scope reason
Public contracts / state shape changes without migration NOT_APPLICABLE No type or state-shape change; AccountState remains decimal strings, src/types/index.ts:932-983. Both consumers inspected above.
Public contracts / method signature compatibility NOT_APPLICABLE No signatures change; the getSpotBalance/getSpotHold number returns remain intact, accountUtils.ts:179-188.
Public contracts / event name or payload drift NOT_APPLICABLE No events changed.
Public contracts / package export tests NOT_APPLICABLE No export additions or removals; public aggregateAccountStates remains exported at src/index.ts:582.
Release metadata / changelog matches impact PASS CHANGELOG.md:12 documents the decimal arithmetic and precision fix under Fixed. This is normalization of existing decimal strings.
Release metadata / breaking change as minor or patch NOT_APPLICABLE No release version is changed and no structural contract break was found.
Release metadata / integration version and compatibility PASS This is not a bump/sync PR. Current package version is 22.1.1; client versions and pinned revisions are recorded above. Both consumer parsers accept the normalized strings.
Multi-sig / proactive probe, classifier and probe placement NOT_APPLICABLE No user-scoped exchange write, setup call, multi-sig handling or catch classifier changes.
Unified setup / permanent condition cached as retryable NOT_APPLICABLE No setup/cache/retry changes. Arithmetic reads the already-resolved fold option.
Unified setup / retry flag plus permanent cache NOT_APPLICABLE No readiness-state or retry-flag changes.
Unified setup / deferred or skipped path as failure NOT_APPLICABLE No unified setup branch changes.
Metered benefits / strict improvement and quantization NOT_APPLICABLE No fee source or metered allowance changes.
Read-only guards / inventory and guard every write NOT_APPLICABLE No read-only claim or write-boundary change.
Expected evidence / contract impact matrix PASS Frozen PR body lists state, methods, events, exports and constants; checked against the complete diff and unchanged public types.
Expected evidence / Mobile and Extension compatibility PASS Frozen body supplies Mobile check and historical Extension limitation. This review independently inspected both supplied pinned checkouts; no paired migration is needed.
Expected evidence / abstraction and fallback tests PASS AggregatedPerpsProvider.ts:479 routes account state via its router. Inspected HyperLiquidProvider.error-handling.test.ts:1336-1403 for total/partial fetch failure, data.test.ts:699-738 for Standard mode, and SubscriptionService.cache.test.ts:1446-1502 for mode-failure retry behavior. Utility empty and sentinel branches were inspected directly.
Expected evidence / platform-import and global grep PASS artifacts/portability-search.txt records no matches for platform imports/globals in the two changed production files. Whole diff introduces only BigNumber, an existing dependency, and package-local imports.

Findings

BLOCKERS: [].

  • nit: packages/perps-controller/src/utils/accountUtils.ts:221. The helper now canonicalizes finite balances even when spotState is null or missing, but this JSDoc still promises the state is returned unchanged. Update the parameter description to mention canonicalization when spot data is absent. This line is outside the RIGHT-side diff, so the note is body-only.

The initial test-sensitivity question was resolved by distinguishing the regression each fixture proves. Tiny fractional fixtures protect deferred rounding; exact spot operand assertions protect the reported failure. They do not prove every floating-point sum direction, and no such runtime claim is made.

Limitations

Test code and source were inspected; tests were not executed. No CI results, app observations, consumer builds or package installation were available as evidence. Missing execution evidence is a reviewer-side limitation under the static-only task, not a claimed pass. Source compatibility at the recorded client revisions does not establish that either installed client includes this package version.

Existing missing-spot zero defaults and total parsing through Number in hyperLiquidAdapter.ts:558-565 are outside this diff's changed arithmetic; the review makes no broader precision claim for arbitrary-sized values.

Recommended action

Approve the contribution; update the JSDoc when convenient. Use the explicit Run QA handoff above for execution proof. Optional additional regression data can include per-DEX 0.1 plus 0.7 with no spot, expecting exactly 0.8 after final canonicalization, and the same values in a streamed callback. Do not treat this static review as runtime or client-build proof.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants