Skip to content

1282.0.0

Choose a tag to compare

@github-actions github-actions released this 24 Sep 08:34
3d047bd

@metamask/perps-controller 18.0.0

Added

  • Add optional subscriptionWaiverKind ('full' | 'partial') and subscriptionCoveredNotionalUsd fields to PerpsFeeResolution, reporting how much of an order the subscription allowance covered. (#10294)
  • Add the perpsSubscriptionFeeWaiverEnabled remote feature flag, which disables the subscription fee source on its own without affecting rewards or the default builder fee. An absent or malformed flag reads as enabled. (#10294)
  • Export the subscription fee-waiver helpers from the utils barrel, including hasFeeReductionAppliedFlag and isSubscriptionProgramCloid for decoding a marked client order ID, and add an exact ./utils subpath export so the barrel is importable as @metamask/perps-controller/utils. (#10294)
  • Add an optional chargesMetamaskBuilderFee field to FeeCalculationResult, which reports whether a placement can carry a MetaMask builder fee at all. A metamaskFeeRate of 0 is otherwise ambiguous between a venue or order type that has no builder field and a fully waived fee. (#10294)
  • Add an optional registerTradingAddress hook to the injected subscription dependency, for registering the current HyperLiquid trading address (CAIP-10) against the subscription profile. The injected hook remains a fallback for clients that do not provide SubscriptionController:registerAddress. (#10294)
  • Add optional fillId field to the OrderFill type, an opaque execution identifier (built from HyperLiquid coin, time and tid, or Lighter tradeId) so clients can tell apart two executions of one order that share orderId, timestamp, size and price (#10384)

Changed

  • BREAKING: PerpsControllerAllowedActions now includes SubscriptionController:getBenefits and the structural SubscriptionController:registerAddress action, so benefits hydration can run over the action @metamask/subscription-controller already exposes. Its micro-USD allowances are converted to USD at the boundary. (#10294)
    • This is a type break for every client, including clients that do not register the action. Messenger requires each child action to exist in the parent action union, so a strict parent messenger type must include both action types before it will build; the registration handler accepts a CAIP-10 string and returns Promise<void>. Runtime behavior is unchanged for those clients — an unregistered action falls back to the injected subscription dependency — but the build does not pass without the type. Coordinate the Mobile and Extension messenger updates with this release.
  • BREAKING: PerpsController.calculateFees now quotes the subscription fee waiver as a blended rate derived from the order notional, so feeRate, feeAmount, metamaskFeeRate, and metamaskFeeAmount can differ from previous releases when a subscription waiver applies. (#10294)
    • Pass the order notional (USD) as FeeCalculationParams.amount. It is now required for quote/submit parity, and omitting it changes the quote rather than preserving the previous one: a waiver whose remaining allowance is bounded is withheld entirely from a quote with no notional, so the preview reports the next-lowest source while a submit that can derive a notional still applies the waiver. A waiver with no reported allowance bound is unaffected.
    • Quoted rates are also repriced when rewards win, not only under a subscription waiver, and are quantized to the venue's tenths of a basis point so a quote equals the charged rate.
  • Resolve the subscription fee waiver as 0 bips when the remaining allowance covers the order notional and MaxFee × (1 − remaining / orderNotional) otherwise, and let that rate compete in the lowest-fee comparison — a partial waiver can now lose to a VIP or season discount. (#10294)
  • Mark the order's client order ID with the subscription program marker and a fee_reduction_applied flag on every placement, replace, TP/SL, batch-close, modify, and chase path when the subscription source wins and actually reduced the fee. Any other fee source leaves the client order ID untouched, as does a subscription waiver whose remaining allowance is too small to change the charged fee. (#10294)
    • A client order ID supplied by the caller through OrderParams.clientOrderId is never rewritten, so such orders are submitted exactly as requested and are not attributed to the subscription program. Only client order IDs this package generates carry the marking, and that provenance is tracked explicitly rather than inferred from the ID's leading bytes — a caller-supplied ID beginning with a reserved marker is still preserved.
    • Scale-ladder client order IDs keep their own group marker and rung index, so group recovery and cancel-by-client-order-ID are unaffected. A ladder's reserved flag byte is set when the waiver applies, but hasFeeReductionAppliedFlag does not report it — see the Fixed entry below. Scale fills and fills on caller-supplied client order IDs therefore receive the discount without a decodable marker; attributing them needs a correlation other than the client order ID.
    • The subscription program marker is the registered id 0x0100, zero-extended into the 4-byte marker field.
  • Register the current HyperLiquid trading address with the subscription profile during calculateFees, and re-register it after the selected account changes. (#10294)
    • The CAIP-10 identifier names HyperLiquid's own chain (eip155:999, or eip155:998 on testnet), not the wallet's currently selected network, so it matches the chain a fill decoded off the HyperLiquid fan-out reports.

Deprecated

  • Deprecate PerpsController.approveSubscriptionBuilderFee, PerpsProvider.approveSubscriptionBuilderFee, and the dedicated subscription builder address configuration. Subscription attribution now rides on the order's client order ID rather than a separate approved builder, so the controller method is a no-op that always resolves true — nothing needs approving, and a false would read as a setup failure to a caller that branches on it. The provider-side approval machinery is retained but unreachable from order construction. (#10294)

Fixed

  • Report source: 'subscription' only when the waiver survives the venue's fee quantization. The builder fee is submitted in integer tenths of a basis point, so a blend a fraction below the default rounds to the same charge; such an order was labelled as subscription-sourced with a 0 bips discount while paying full price, disagreeing with the client order ID, which already withheld its marking in that case. (#10294)
  • Leave a fee tie to the source that already holds it, rather than claiming it for the subscription waiver. A blend that merely matched the winning rate — a 5-bip blend against a 5-bip VIP discount, or a full waiver against a rewards rate already at 0 — was reported as source: 'subscription', which marks the client order ID and spends the remaining allowance without making the order any cheaper. The waiver now has to be strictly cheaper at venue precision to win. (#10294)
  • Reprice a quote whose metamaskFeeRate reads 0 because a concurrent fully waived submit left that rate in provider state. An ordinary preview racing such a submit inherited the other order's waiver and quoted no MetaMask fee; the provider's own builder-fee policy now distinguishes that from a placement that genuinely carries no fee. Repricing a 0 rate is opt-in: a provider that does not report chargesMetamaskBuilderFee keeps its own rate, so a PerpsProvider implementation written before that field existed cannot gain a MetaMask fee it does not charge. (#10294)
  • Reject client order IDs that match the subscription program marker and length but contain non-hex characters from isSubscriptionProgramCloid, which no longer treats such a value as a marked ID. (#10294)
  • Trust the fee_reduction_applied flag only on a client order ID carrying the subscription program marker. The flag byte occupies a position that held random entropy in Scale-ladder client order IDs placed before this release, so reading it on any other client order ID reports roughly half of those historical ladders as fee-waived. As a result hasFeeReductionAppliedFlag returns false for a marked Scale rung, which keeps its own group marker; Scale attribution needs a correlation other than the client order ID. (#10294)
  • Mark the client order ID of every chase replacement when the chase was placed under a subscription waiver. The marking read the live fee resolution, which the trading service clears as soon as the initial placement returns, so a replacement paid the discounted fee the session captured while shipping an unmarked ID. The decision is now captured with the session's builder fee, for the same reason. (#10294)
  • Withhold the subscription waiver when the allowance is bounded and the order notional cannot be determined. Such an order previously resolved as a full waiver, charging nothing on an order of unknown size and over-consuming the allowance. An unbounded allowance is unaffected. (#10294)
  • Price a batch close from the positions of the provider that submits it. The notional previously summed every aggregated provider's positions, while the batch routes to one, which could inflate the notional and shrink the waiver. (#10294)
  • Honor closeAll ahead of symbols when pricing a batch close, matching the provider's own selection precedence. A closeAll request that also carried a symbol list was priced from the filtered subset while the batch closed every position, so the waiver was granted against a smaller notional than the order submitted. (#10294)
  • Price a partial TP/SL update from the trigger size it submits rather than the whole position. takeProfitSize and stopLossSize are honored by the provider, so a partial trigger on a large position was priced against the full position notional and blended a fee on an order the remaining allowance covered outright. The larger of the two sizes prices the action, since both triggers are submitted under one builder context. (#10294)
  • Price each TP/SL trigger from its own price and absolute size, using the full position size when a trigger size is omitted. If any included trigger cannot be priced, withhold a bounded waiver for the batch. (#10294)
  • Discard a trading-address registration whose profile was invalidated while it was in flight. The completion previously cached the address regardless, so a new profile reusing that address skipped its own registration and left its fills unattributable. (#10294)
  • Preserve a cached benefits snapshot when a registered SubscriptionController:getBenefits handler rejects or throws synchronously and no injected subscription dependency exists to fall back to. The rejection was previously swallowed and stored as a successful "no subscription" result, erasing a waiver the user was still entitled to. (#10294)
  • Drop a cached subscription waiver when SubscriptionController:getBenefits reports the profile is not subscribed. That rejection is a definitive answer rather than a failed read — the controller clears its own benefits state on the same path — so preserving the cached snapshot kept granting the waiver for the rest of the staleness window after entitlement ended. (#10294)
  • Stop marking the client order ID of an order edit. HyperLiquid's modify action carries no builder field, so no MetaMask fee is charged on it, and marking reported a fee reduction on an order that paid nothing. The replacement inherits the resting order's own attribution. (#10294)
  • Price a trigger placement (stop or take-profit) from its trigger price. Such an order carries no limit price, so its notional could not be derived and a bounded waiver was withheld from an order the provider prices later. (#10294)
  • Price a Scale ladder from the midpoint of scaleMinPrice and scaleMaxPrice. A Scale placement states no single price, so its notional could not be derived and a bounded waiver was withheld at submit after a preview that quoted one. A stated usdAmount still takes precedence. Chase and TWAP placements state no price at all and remain priced from usdAmount or the quoted market price. (#10294)
  • Read the subscription metadata attached to a fee preview from the same resolution as the quoted rates. They were two separate reads, so a cache invalidation or feature-flag change between them could return metadata describing a waiver the rates did not reflect. (#10294)
  • Require positive evidence of a perps benefit before granting the waiver. products.perps is always present on the benefits response, so its existence proved nothing: a profile eligible for other products but carrying no perps builder fee, allowance or cap was granted an unbounded full waiver. (#10294)
  • Reject a malformed client order ID in hasFeeReductionAppliedFlag and readSubscriptionCloidFlags. Only length and prefix were validated, so a flag byte such as 1z parsed as 1 and reported the order as fee-reduced. (#10294)
  • Leave quoted fee amounts untouched when the supplied order notional is zero or negative. Such a value is not an order size and previously produced negative feeAmount and metamaskFeeAmount figures. (#10294)
  • Distinguish an unregistered benefits action from a handler that throws on its first call. The latter was treated as the former and cached as a successful "no subscription" result. (#10294)
  • Reprice a fee preview to the undiscounted builder fee when the default source wins, rather than returning the provider's own rate. The provider's rate reflects the discount the last submit pushed into it, so a concurrent order could leak its discount into an unrelated quote. (#10294)
  • Price a take-profit/stop-loss update from the position read back through the routed provider when the caller supplies neither a position snapshot nor tracking data. Both are optional, and a bounded waiver is withheld without a notional, so a valid update silently lost the waiver. (#10294)
  • Price a close from the position the write can actually reach. A close read positions across every active provider and matched on symbol alone, so in aggregated mode a batch close summed positions it could not close, and a routed single close could price another provider's position for the same symbol. (#10294)
  • Quantize the previewed MetaMask builder fee to the venue's tenths of a basis point, matching what submit charges. A blended rate of 6.667 bips was previously quoted as 6.667 and charged as 6.6. (#10294)
  • Hydrate subscription benefits for a client that registers SubscriptionController:getBenefits without also injecting the optional subscription dependency. Both the eligibility read and the benefits refresh previously required the injected dependency, so a client adopting only the controller action always resolved as having no subscription source and never received a waiver. (#10294)
  • Price a position close from the loaded position when the close parameters do not carry a notional. A full close commonly passes only a symbol, which previously resolved as an unbounded waiver rather than blending against the position's value; a partial close is now priced from the position's value per unit. (#10294)
  • Resolve the subscription fee waiver against the order notional on the submit path, not just in previews. Order placement, order edits, position closes, batch closes, take-profit/stop-loss updates, and position flips previously resolved the waiver with no notional, so a bounded allowance always resolved as a full waiver — an order was quoted a blended rate and then charged nothing, over-consuming the allowance and marking its client order ID as fully waived. (#10294)
  • Register the newly selected trading address immediately on an account switch. Clearing the session's registrations alone only re-registered on the next fee preview, so an order submitted straight after a switch went unattributed. (#10294)