Skip to content

feat(perps-controller): add scaleSkew to Scale orders - #9919

Draft
abretonc7s wants to merge 2 commits into
mainfrom
TAT-3817-feat-add-scale-skew-support
Draft

feat(perps-controller): add scaleSkew to Scale orders#9919
abretonc7s wants to merge 2 commits into
mainfrom
TAT-3817-feat-add-scale-skew-support

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Explanation

TAT-3723 added the scale order type to @metamask/perps-controller as scaleMinPrice / scaleMaxPrice / scaleNumOrders, with the size spread evenly across the ladder. The product spec calls for weighting that ladder toward one end of the range, and there was no parameter for it — which blocks TAT-3812, where Size Skew is a user-facing input on the mobile order form.

This adds OrderParams.scaleSkew?: number.

Behaviour. Rung weights ramp linearly from 1 at index 0 to scaleSkew at the last index, in ladder order — ascending price, scaleMinPrice to scaleMaxPrice, for a buy and a sell alike. It does not flip for a short. Above 1 puts more size at scaleMaxPrice, below 1 at scaleMinPrice. Omitted or exactly 1 takes the existing even-split path with no behaviour change.

Allocation, and why the two paths differ. Sizes are allocated in whole units of the asset's size grid, never in floating notional that is re-floored later, so the rungs sum to exactly the size that was validated. The skewed path floors each rung to floor(weight / sumOfWeights × totalUnits) and gives the leftover units to the rungs with the largest discarded fraction, ties by ascending index. The even path keeps putting its whole leftover on the first rung — 11 units across 3 rungs is still 5, 3, 3. That asymmetry is deliberate: dumping the leftover on rung 0 under a skew above 1 would push size back toward the end of the ladder the caller weighted away from.

Validation. scaleSkew is registered as a scale-owned strategy field, so carrying it on any other order type is rejected with the existing ORDER_STRATEGY_PARAMS_NOT_SUPPORTED rather than silently ignored. A supplied value that is not a finite number above 0 is rejected by validateOrderParams with a new ORDER_SCALE_SKEW_INVALID, with no network call and nothing signed. The value is used exactly as given — clients coerce their input to two decimals, and re-rounding here would place a ladder weighted differently from the one the form previewed.

Per-rung minimums needed no new check. #buildScaleLadder already applies the venue's per-order minimum to the real grid sizes before anything is signed, and validateStrategyNotional deliberately skips scale for that reason. A skew that pushes a rung under the minimum, or onto a zero size-grid slice, is refused there with the existing ORDER_SCALE_NOTIONAL_TOO_SMALL / ORDER_SCALE_SIZE_TOO_SMALL — the batch order action is never sent.

Notable API effects. splitScaleSizes gains an optional skew parameter and stays the single source of truth for the ladder's sizes; extending it rather than adding a sibling export means a client previewing a ladder and the placement path cannot disagree at the rounding. The remainder rule for both paths is documented on its JSDoc. The signature change is source-compatible for existing callers, and ORDER_SCALE_SKEW_INVALID is additive to the PerpsErrorCode union. Suborders remain GTC, rung count stays SCALE_ORDER_COUNT (2..20), and total ladder margin is still checked at placement.

Validation run. Three targeted suites (212 tests) covering the ramp in both directions, the tie-break, grid-unit sum invariants, very high skew, the starved-rung boundary, invalid values, and skew-1 equivalence with the untouched even path; a provider test asserting the exact skewed sizes reach exchangeClient.order, and that order is never called on a refusal. Changed-file ESLint, Prettier and jest all pass, the changelog validates, and the root build emits skew?: number into the published type declarations. Existing even-distribution scale placement and the other order types are unchanged.

References

  • https://consensyssoftware.atlassian.net/browse/TAT-3817
  • Follow-up to TAT-3723 (Chase, TWAP and Scale order types)
  • Unblocks TAT-3812 (Mobile | Pro mode | Orders | Scale) — the mobile preview should call the exported splitScaleSizes rather than reimplementing the ramp. Note that package.json exposes ./utils/* and not a bare ./utils, so the import is either @metamask/perps-controller (root) or @metamask/perps-controller/utils/index.

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 — not applicable: scaleSkew is optional, the splitScaleSizes signature change is source-compatible for existing callers, and the new error code is additive

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.

1 participant