@metamask-previews/perps-controller 20.1.0-preview-b4b0d7a09 → 21.0.0-preview-55fcf3ef7

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.
@@ -426,12 +426,13 @@ export class TradingService {
426
426
  *
427
427
  * @param options - The configuration options.
428
428
  * @param options.provider - The perps provider instance.
429
- * @param options.feeResolution - The resolved fee and attribution source.
429
+ * @param options.resolveFee - Resolves the fee after this operation acquires
430
+ * the fee context, so an expiring candidate cannot go stale in the queue.
430
431
  * @param options.operation - The operation value.
431
432
  * @returns The result of the operation.
432
433
  */
433
434
  async #withFeeDiscount(options) {
434
- const { provider, feeResolution, operation } = options;
435
+ const { provider, resolveFee, operation } = options;
435
436
  const previous = this.#feeContextTail;
436
437
  let release = () => undefined;
437
438
  this.#feeContextTail = new Promise((resolve) => {
@@ -439,6 +440,7 @@ export class TradingService {
439
440
  });
440
441
  await previous;
441
442
  try {
443
+ const feeResolution = await resolveFee();
442
444
  if (provider.setUserFeeResolution) {
443
445
  provider.setUserFeeResolution(feeResolution);
444
446
  }
@@ -531,13 +533,6 @@ export class TradingService {
531
533
  pay_token_symbol: paymentToken,
532
534
  },
533
535
  });
534
- // Calculate fee discount at execution time (fresh, secure)
535
- const feeResolution = await this.#calculateFeeDiscountWithMeasurement(this.#resolveOrderNotionalUsd(params));
536
- this.#deps.debugLogger.log('TradingService: Fee resolution calculated', {
537
- feeDiscountBips: feeResolution?.discountBips,
538
- feeSource: feeResolution?.source,
539
- hasDiscount: feeResolution?.discountBips !== undefined,
540
- });
541
536
  this.#deps.debugLogger.log('TradingService: Submitting order to provider', {
542
537
  symbol: params.symbol,
543
538
  orderType: params.orderType,
@@ -587,7 +582,15 @@ export class TradingService {
587
582
  }, PERPS_CONSTANTS.PlaceOrderTimeoutMs);
588
583
  const result = await this.#withFeeDiscount({
589
584
  provider,
590
- feeResolution,
585
+ resolveFee: async () => {
586
+ const feeResolution = await this.#calculateFeeDiscountWithMeasurement(this.#resolveOrderNotionalUsd(params), provider, context, params.providerId);
587
+ this.#deps.debugLogger.log('TradingService: Fee resolution calculated', {
588
+ feeDiscountBips: feeResolution?.discountBips,
589
+ feeSource: feeResolution?.source,
590
+ hasDiscount: feeResolution?.discountBips !== undefined,
591
+ });
592
+ return feeResolution;
593
+ },
591
594
  operation: () => provider.placeOrder(params),
592
595
  });
593
596
  if (orderSubmissionThresholdTimeoutId !== undefined) {
@@ -1155,10 +1158,13 @@ export class TradingService {
1155
1158
  * priced. This is what lets the subscription source resolve to a blended
1156
1159
  * rate: omitting it resolves every bounded allowance as a full waiver,
1157
1160
  * charging 0 bips on an order the preview quoted a blend for.
1161
+ * @param provider - The actual provider receiving this submission.
1162
+ * @param context - The submission context, including its network.
1163
+ * @param providerId - Explicit route, when the caller supplied one.
1158
1164
  * @returns The resolved fee, or undefined when controller dependencies are
1159
1165
  * unavailable.
1160
1166
  */
1161
- async #calculateFeeDiscountWithMeasurement(orderNotionalUsd) {
1167
+ async #calculateFeeDiscountWithMeasurement(orderNotionalUsd, provider, context, providerId) {
1162
1168
  // Check if controller dependencies are available
1163
1169
  if (!this.#controllerDeps) {
1164
1170
  this.#deps.debugLogger.log('TradingService: Controller dependencies not set, skipping fee discount');
@@ -1169,7 +1175,10 @@ export class TradingService {
1169
1175
  // The notional is what lets the subscription source resolve to a blended
1170
1176
  // rate. Submitting without it would resolve every bounded allowance as a
1171
1177
  // full waiver, charging 0 bips on an order the preview quoted a blend for.
1172
- const resolution = await rewardsIntegrationService.resolveFee(orderNotionalUsd);
1178
+ const resolution = await rewardsIntegrationService.resolveFee(orderNotionalUsd, {
1179
+ providerId: provider.getWriteProviderId?.(providerId) ?? provider.protocolId,
1180
+ isTestnet: context.tracingContext.isTestnet,
1181
+ });
1173
1182
  const orderExecutionFeeDiscountDuration = this.#deps.performance.now() - orderExecutionFeeDiscountStartTime;
1174
1183
  // Record measurement
1175
1184
  this.#deps.tracer.setMeasurement(PerpsMeasurementName.PerpsRewardsOrderExecutionFeeDiscountApiCall, orderExecutionFeeDiscountDuration, 'millisecond');
@@ -1214,12 +1223,10 @@ export class TradingService {
1214
1223
  orderPrice: params.newOrder.price ?? '',
1215
1224
  },
1216
1225
  });
1217
- // Calculate fee discount only if required dependencies are available
1218
- const feeResolution = await this.#calculateFeeDiscountWithMeasurement(this.#resolveOrderNotionalUsd(params.newOrder));
1219
1226
  // Execute order edit with fee discount management
1220
1227
  const result = await this.#withFeeDiscount({
1221
1228
  provider,
1222
- feeResolution,
1229
+ resolveFee: () => this.#calculateFeeDiscountWithMeasurement(this.#resolveOrderNotionalUsd(params.newOrder), provider, context, params.newOrder.providerId),
1223
1230
  operation: () => provider.editOrder(params),
1224
1231
  });
1225
1232
  const completionDuration = this.#deps.performance.now() - startTime;
@@ -1628,7 +1635,7 @@ export class TradingService {
1628
1635
  // only a symbol, so `params` alone prices it as undefined and the resolver
1629
1636
  // would quote a full waiver on an order the preview blended. The position
1630
1637
  // loaded above is the authoritative notional for exactly that case.
1631
- const feeResolution = await this.#calculateFeeDiscountWithMeasurement(this.#resolveOrderNotionalUsd({
1638
+ const closeNotionalUsd = this.#resolveOrderNotionalUsd({
1632
1639
  ...params,
1633
1640
  // A partial close names a size but often no price; the position's own
1634
1641
  // value per unit prices it. A full close names neither, and falls back
@@ -1637,11 +1644,11 @@ export class TradingService {
1637
1644
  }) ??
1638
1645
  this.#resolveOrderNotionalUsd({
1639
1646
  usdAmount: position?.positionValue,
1640
- }));
1647
+ });
1641
1648
  // Execute position close with fee discount management
1642
1649
  result = await this.#withFeeDiscount({
1643
1650
  provider,
1644
- feeResolution,
1651
+ resolveFee: () => this.#calculateFeeDiscountWithMeasurement(closeNotionalUsd, provider, context, params.providerId),
1645
1652
  operation: () => provider.closePosition(params),
1646
1653
  });
1647
1654
  const completionDuration = this.#deps.performance.now() - startTime;
@@ -1758,10 +1765,13 @@ export class TradingService {
1758
1765
  if (provider.closePositions) {
1759
1766
  // The batch submits under one builder context, so its notional is the
1760
1767
  // sum of the positions it will close, not any single one of them.
1761
- const feeResolution = await this.#calculateFeeDiscountWithMeasurement(await this.#resolveBatchCloseNotionalUsd({ params, provider }));
1768
+ const batchCloseNotionalUsd = await this.#resolveBatchCloseNotionalUsd({
1769
+ params,
1770
+ provider,
1771
+ });
1762
1772
  operationResult = await this.#withFeeDiscount({
1763
1773
  provider,
1764
- feeResolution,
1774
+ resolveFee: () => this.#calculateFeeDiscountWithMeasurement(batchCloseNotionalUsd, provider, context),
1765
1775
  operation: async () => {
1766
1776
  if (!provider.closePositions) {
1767
1777
  throw new Error('closePositions method not available');
@@ -1964,11 +1974,10 @@ export class TradingService {
1964
1974
  pricedTriggerNotionalsUsd.length === triggerNotionalsUsd.length
1965
1975
  ? Math.max(...pricedTriggerNotionalsUsd)
1966
1976
  : undefined;
1967
- const feeResolution = await this.#calculateFeeDiscountWithMeasurement(tpslNotionalUsd);
1968
1977
  // Execute with fee discount management
1969
1978
  result = await this.#withFeeDiscount({
1970
1979
  provider,
1971
- feeResolution,
1980
+ resolveFee: () => this.#calculateFeeDiscountWithMeasurement(tpslNotionalUsd, provider, context, params.providerId),
1972
1981
  operation: () => provider.updatePositionTPSL(params),
1973
1982
  });
1974
1983
  if (result.success) {
@@ -2207,12 +2216,17 @@ export class TradingService {
2207
2216
  // Use 2x position size: 1x to close current position + 1x to open opposite position.
2208
2217
  // Do not pass the position entry price as currentPrice: the provider must fetch
2209
2218
  // live market data for validation and IOC pricing.
2219
+ // Carry the position venue so aggregated placeOrder reaches the same
2220
+ // provider the fee grant below is scoped to.
2210
2221
  const orderParams = {
2211
2222
  symbol: position.symbol,
2212
2223
  isBuy: oppositeDirection,
2213
2224
  size: flipSize.toString(),
2214
2225
  orderType: 'market',
2215
2226
  leverage: position.leverage?.value,
2227
+ ...(position.providerId === undefined
2228
+ ? {}
2229
+ : { providerId: position.providerId }),
2216
2230
  // Lighter placeOrder otherwise logs order_management/place_order and
2217
2231
  // marks the result reported, which drops this flip from flip filters.
2218
2232
  // Close already suppresses that provider report. One Sentry event remains.
@@ -2237,11 +2251,10 @@ export class TradingService {
2237
2251
  const flipNotionalUsd = this.#resolveOrderNotionalUsd({
2238
2252
  usdAmount: position.positionValue,
2239
2253
  });
2240
- const feeResolution = await this.#calculateFeeDiscountWithMeasurement(flipNotionalUsd === undefined ? undefined : flipNotionalUsd * 2);
2241
2254
  // Place flip order (HyperLiquid handles margin transfer automatically)
2242
2255
  const result = await this.#withFeeDiscount({
2243
2256
  provider,
2244
- feeResolution,
2257
+ resolveFee: () => this.#calculateFeeDiscountWithMeasurement(flipNotionalUsd === undefined ? undefined : flipNotionalUsd * 2, provider, context, position.providerId),
2245
2258
  operation: () => provider.placeOrder(orderParams),
2246
2259
  });
2247
2260
  const completionDuration = this.#deps.performance.now() - startTime;