@metamask/transaction-pay-controller 23.13.1 → 23.15.0

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/dist/strategy/fiat/fiat-direct-musd.cjs +7 -1
  3. package/dist/strategy/fiat/fiat-direct-musd.cjs.map +1 -1
  4. package/dist/strategy/fiat/fiat-direct-musd.d.cts.map +1 -1
  5. package/dist/strategy/fiat/fiat-direct-musd.d.mts.map +1 -1
  6. package/dist/strategy/fiat/fiat-direct-musd.mjs +7 -1
  7. package/dist/strategy/fiat/fiat-direct-musd.mjs.map +1 -1
  8. package/dist/strategy/relay/constants.cjs +4 -2
  9. package/dist/strategy/relay/constants.cjs.map +1 -1
  10. package/dist/strategy/relay/constants.d.cts +2 -0
  11. package/dist/strategy/relay/constants.d.cts.map +1 -1
  12. package/dist/strategy/relay/constants.d.mts +2 -0
  13. package/dist/strategy/relay/constants.d.mts.map +1 -1
  14. package/dist/strategy/relay/constants.mjs +3 -1
  15. package/dist/strategy/relay/constants.mjs.map +1 -1
  16. package/dist/strategy/relay/hyperliquid-activation.cjs +155 -0
  17. package/dist/strategy/relay/hyperliquid-activation.cjs.map +1 -0
  18. package/dist/strategy/relay/hyperliquid-activation.d.cts +46 -0
  19. package/dist/strategy/relay/hyperliquid-activation.d.cts.map +1 -0
  20. package/dist/strategy/relay/hyperliquid-activation.d.mts +46 -0
  21. package/dist/strategy/relay/hyperliquid-activation.d.mts.map +1 -0
  22. package/dist/strategy/relay/hyperliquid-activation.mjs +150 -0
  23. package/dist/strategy/relay/hyperliquid-activation.mjs.map +1 -0
  24. package/dist/strategy/relay/relay-quotes.cjs +31 -13
  25. package/dist/strategy/relay/relay-quotes.cjs.map +1 -1
  26. package/dist/strategy/relay/relay-quotes.d.cts.map +1 -1
  27. package/dist/strategy/relay/relay-quotes.d.mts.map +1 -1
  28. package/dist/strategy/relay/relay-quotes.mjs +31 -13
  29. package/dist/strategy/relay/relay-quotes.mjs.map +1 -1
  30. package/dist/strategy/relay/relay-submit.cjs +6 -1
  31. package/dist/strategy/relay/relay-submit.cjs.map +1 -1
  32. package/dist/strategy/relay/relay-submit.mjs +6 -1
  33. package/dist/strategy/relay/relay-submit.mjs.map +1 -1
  34. package/dist/tests/messenger-mock.d.cts +2 -2
  35. package/dist/tests/messenger-mock.d.mts +2 -2
  36. package/dist/types.cjs.map +1 -1
  37. package/dist/types.d.cts +7 -0
  38. package/dist/types.d.cts.map +1 -1
  39. package/dist/types.d.mts +7 -0
  40. package/dist/types.d.mts.map +1 -1
  41. package/dist/types.mjs.map +1 -1
  42. package/dist/utils/feature-flags.cjs +37 -1
  43. package/dist/utils/feature-flags.cjs.map +1 -1
  44. package/dist/utils/feature-flags.d.cts +23 -0
  45. package/dist/utils/feature-flags.d.cts.map +1 -1
  46. package/dist/utils/feature-flags.d.mts +23 -0
  47. package/dist/utils/feature-flags.d.mts.map +1 -1
  48. package/dist/utils/feature-flags.mjs +35 -0
  49. package/dist/utils/feature-flags.mjs.map +1 -1
  50. package/package.json +5 -5
@@ -0,0 +1,46 @@
1
+ import type { QuoteRequest, TransactionPayControllerMessenger } from "../../types.cjs";
2
+ /**
3
+ * Minimal shape of a HyperLiquid `userNonFundingLedgerUpdates` entry. Only the
4
+ * fields used to determine transfer direction are typed.
5
+ */
6
+ export type HyperLiquidLedgerUpdate = {
7
+ delta?: {
8
+ type?: string;
9
+ user?: string;
10
+ destination?: string;
11
+ };
12
+ };
13
+ /**
14
+ * Whether the HyperCore account has already paid the activation fee.
15
+ *
16
+ * The fee is charged on the account's first outbound send/withdrawal, so an
17
+ * account that has only ever received deposits (or has no non-funding ledger
18
+ * history) is treated as unactivated. Direction matters: only entries the
19
+ * account itself initiated count.
20
+ *
21
+ * @param updates - Raw HyperLiquid non-funding ledger updates.
22
+ * @param address - The account's address.
23
+ * @returns True when the account has made a prior outbound transfer.
24
+ */
25
+ export declare function isHyperLiquidAccountActivated(updates: HyperLiquidLedgerUpdate[], address: string): boolean;
26
+ /**
27
+ * Reserve the one-time HyperLiquid activation fee for an unactivated HyperCore
28
+ * source account.
29
+ *
30
+ * Reduces the amount sent to the provider so HyperLiquid retains enough balance
31
+ * for the activation fee on the `sendAsset` step, and records the reserved fee
32
+ * (USD) so it can be added to the provider fee — keeping the displayed
33
+ * withdrawal amount unchanged.
34
+ *
35
+ * No-op for non-HyperLiquid sources, when the feature flag is disabled, when
36
+ * the account is already activated, or when the amount is too small to reserve.
37
+ *
38
+ * @param request - Normalized quote request.
39
+ * @param messenger - Controller messenger.
40
+ * @param transactionType - Parent transaction type used to resolve the feature
41
+ * flag override.
42
+ * @param signal - Optional abort signal forwarded to the activation request.
43
+ * @returns The (possibly adjusted) quote request.
44
+ */
45
+ export declare function applyHyperliquidActivationFee(request: QuoteRequest, messenger: TransactionPayControllerMessenger, transactionType?: string, signal?: AbortSignal): Promise<QuoteRequest>;
46
+ //# sourceMappingURL=hyperliquid-activation.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hyperliquid-activation.d.cts","sourceRoot":"","sources":["../../../src/strategy/relay/hyperliquid-activation.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,YAAY,EACZ,iCAAiC,EAClC,wBAAoB;AAcrB;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH,CAAC;AAmCF;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,uBAAuB,EAAE,EAClC,OAAO,EAAE,MAAM,GACd,OAAO,CAUT;AA8CD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,iCAAiC,EAC5C,eAAe,CAAC,EAAE,MAAM,EACxB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,YAAY,CAAC,CA4CvB"}
@@ -0,0 +1,46 @@
1
+ import type { QuoteRequest, TransactionPayControllerMessenger } from "../../types.mjs";
2
+ /**
3
+ * Minimal shape of a HyperLiquid `userNonFundingLedgerUpdates` entry. Only the
4
+ * fields used to determine transfer direction are typed.
5
+ */
6
+ export type HyperLiquidLedgerUpdate = {
7
+ delta?: {
8
+ type?: string;
9
+ user?: string;
10
+ destination?: string;
11
+ };
12
+ };
13
+ /**
14
+ * Whether the HyperCore account has already paid the activation fee.
15
+ *
16
+ * The fee is charged on the account's first outbound send/withdrawal, so an
17
+ * account that has only ever received deposits (or has no non-funding ledger
18
+ * history) is treated as unactivated. Direction matters: only entries the
19
+ * account itself initiated count.
20
+ *
21
+ * @param updates - Raw HyperLiquid non-funding ledger updates.
22
+ * @param address - The account's address.
23
+ * @returns True when the account has made a prior outbound transfer.
24
+ */
25
+ export declare function isHyperLiquidAccountActivated(updates: HyperLiquidLedgerUpdate[], address: string): boolean;
26
+ /**
27
+ * Reserve the one-time HyperLiquid activation fee for an unactivated HyperCore
28
+ * source account.
29
+ *
30
+ * Reduces the amount sent to the provider so HyperLiquid retains enough balance
31
+ * for the activation fee on the `sendAsset` step, and records the reserved fee
32
+ * (USD) so it can be added to the provider fee — keeping the displayed
33
+ * withdrawal amount unchanged.
34
+ *
35
+ * No-op for non-HyperLiquid sources, when the feature flag is disabled, when
36
+ * the account is already activated, or when the amount is too small to reserve.
37
+ *
38
+ * @param request - Normalized quote request.
39
+ * @param messenger - Controller messenger.
40
+ * @param transactionType - Parent transaction type used to resolve the feature
41
+ * flag override.
42
+ * @param signal - Optional abort signal forwarded to the activation request.
43
+ * @returns The (possibly adjusted) quote request.
44
+ */
45
+ export declare function applyHyperliquidActivationFee(request: QuoteRequest, messenger: TransactionPayControllerMessenger, transactionType?: string, signal?: AbortSignal): Promise<QuoteRequest>;
46
+ //# sourceMappingURL=hyperliquid-activation.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hyperliquid-activation.d.mts","sourceRoot":"","sources":["../../../src/strategy/relay/hyperliquid-activation.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,YAAY,EACZ,iCAAiC,EAClC,wBAAoB;AAcrB;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH,CAAC;AAmCF;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,EAAE,uBAAuB,EAAE,EAClC,OAAO,EAAE,MAAM,GACd,OAAO,CAUT;AA8CD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,iCAAiC,EAC5C,eAAe,CAAC,EAAE,MAAM,EACxB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,YAAY,CAAC,CA4CvB"}
@@ -0,0 +1,150 @@
1
+ import { createModuleLogger } from "@metamask/utils";
2
+ import { BigNumber } from "bignumber.js";
3
+ import { HYPERCORE_USDC_DECIMALS } from "../../constants.mjs";
4
+ import { projectLogger } from "../../logger.mjs";
5
+ import { getHyperliquidActivationFeeConfig } from "../../utils/feature-flags.mjs";
6
+ import { HYPERLIQUID_INFO_URL } from "./constants.mjs";
7
+ const log = createModuleLogger(projectLogger, 'hyperliquid-activation');
8
+ /**
9
+ * HyperLiquid non-funding ledger `delta` types that represent an outbound
10
+ * transfer initiated by the account (and therefore pay the activation fee).
11
+ * A funding deposit can arrive as an inbound `send`, so direction is checked
12
+ * for transfer types via the `user`/`destination` fields.
13
+ */
14
+ const OUTBOUND_TRANSFER_TYPES = new Set(['send', 'spotTransfer']);
15
+ /**
16
+ * Whether a single ledger entry is an outbound action initiated by the account.
17
+ *
18
+ * @param update - The ledger entry.
19
+ * @param normalizedAddress - The account's lowercased address.
20
+ * @returns True when the account itself sent funds out.
21
+ */
22
+ function isOutboundFromAccount(update, normalizedAddress) {
23
+ const delta = update?.delta;
24
+ if (!delta?.type) {
25
+ return false;
26
+ }
27
+ // Bridge withdrawals are always outbound from the account.
28
+ if (delta.type === 'withdraw') {
29
+ return true;
30
+ }
31
+ // A spot send/transfer counts only when this account is the sender and the
32
+ // funds leave it (an inbound receipt has `user` set to the other party).
33
+ if (OUTBOUND_TRANSFER_TYPES.has(delta.type)) {
34
+ const sender = delta.user?.toLowerCase();
35
+ const destination = delta.destination?.toLowerCase();
36
+ return sender === normalizedAddress && destination !== normalizedAddress;
37
+ }
38
+ return false;
39
+ }
40
+ /**
41
+ * Whether the HyperCore account has already paid the activation fee.
42
+ *
43
+ * The fee is charged on the account's first outbound send/withdrawal, so an
44
+ * account that has only ever received deposits (or has no non-funding ledger
45
+ * history) is treated as unactivated. Direction matters: only entries the
46
+ * account itself initiated count.
47
+ *
48
+ * @param updates - Raw HyperLiquid non-funding ledger updates.
49
+ * @param address - The account's address.
50
+ * @returns True when the account has made a prior outbound transfer.
51
+ */
52
+ export function isHyperLiquidAccountActivated(updates, address) {
53
+ if (!address) {
54
+ return false;
55
+ }
56
+ const normalizedAddress = address.toLowerCase();
57
+ return updates.some((update) => isOutboundFromAccount(update, normalizedAddress));
58
+ }
59
+ /**
60
+ * Query HyperLiquid for the account's activation state.
61
+ *
62
+ * On any error (network failure, non-OK response, malformed body) the account
63
+ * is treated as activated so the common path is never penalised by a transient
64
+ * HyperLiquid outage; an unactivated account would then surface the original
65
+ * HyperLiquid error, matching the pre-feature behaviour.
66
+ *
67
+ * @param address - The HyperCore account address.
68
+ * @param signal - Optional abort signal forwarded to the request.
69
+ * @returns True when the account is activated (or activation can't be determined).
70
+ */
71
+ async function fetchIsAccountActivated(address, signal) {
72
+ try {
73
+ const response = await fetch(HYPERLIQUID_INFO_URL, {
74
+ method: 'POST',
75
+ headers: { 'Content-Type': 'application/json' },
76
+ body: JSON.stringify({
77
+ type: 'userNonFundingLedgerUpdates',
78
+ user: address,
79
+ startTime: 0,
80
+ }),
81
+ signal,
82
+ });
83
+ if (!response.ok) {
84
+ log('Activation check returned non-OK, assuming activated', {
85
+ status: response.status,
86
+ });
87
+ return true;
88
+ }
89
+ const updates = (await response.json());
90
+ return isHyperLiquidAccountActivated(updates, address);
91
+ }
92
+ catch (error) {
93
+ log('Activation check failed, assuming activated', { error });
94
+ return true;
95
+ }
96
+ }
97
+ /**
98
+ * Reserve the one-time HyperLiquid activation fee for an unactivated HyperCore
99
+ * source account.
100
+ *
101
+ * Reduces the amount sent to the provider so HyperLiquid retains enough balance
102
+ * for the activation fee on the `sendAsset` step, and records the reserved fee
103
+ * (USD) so it can be added to the provider fee — keeping the displayed
104
+ * withdrawal amount unchanged.
105
+ *
106
+ * No-op for non-HyperLiquid sources, when the feature flag is disabled, when
107
+ * the account is already activated, or when the amount is too small to reserve.
108
+ *
109
+ * @param request - Normalized quote request.
110
+ * @param messenger - Controller messenger.
111
+ * @param transactionType - Parent transaction type used to resolve the feature
112
+ * flag override.
113
+ * @param signal - Optional abort signal forwarded to the activation request.
114
+ * @returns The (possibly adjusted) quote request.
115
+ */
116
+ export async function applyHyperliquidActivationFee(request, messenger, transactionType, signal) {
117
+ if (!request.isHyperliquidSource) {
118
+ return request;
119
+ }
120
+ const { enabled, amountUsd } = getHyperliquidActivationFeeConfig(messenger, transactionType);
121
+ if (!enabled) {
122
+ return request;
123
+ }
124
+ const activated = await fetchIsAccountActivated(request.from, signal);
125
+ if (activated) {
126
+ return request;
127
+ }
128
+ const feeRaw = new BigNumber(amountUsd).shiftedBy(HYPERCORE_USDC_DECIMALS);
129
+ const reducedAmount = new BigNumber(request.sourceTokenAmount).minus(feeRaw);
130
+ // Can't reserve more than the balance — let the original amount through so
131
+ // the existing HyperLiquid error surfaces (withdrawable <= activation fee).
132
+ if (reducedAmount.lte(0)) {
133
+ log('Skipping activation reserve as amount is not greater than fee', {
134
+ sourceTokenAmount: request.sourceTokenAmount,
135
+ feeRaw: feeRaw.toFixed(0),
136
+ });
137
+ return request;
138
+ }
139
+ log('Reserving HyperLiquid activation fee', {
140
+ amountUsd,
141
+ originalSourceTokenAmount: request.sourceTokenAmount,
142
+ reducedSourceTokenAmount: reducedAmount.toFixed(0),
143
+ });
144
+ return {
145
+ ...request,
146
+ sourceTokenAmount: reducedAmount.toFixed(0),
147
+ hyperliquidActivationFeeUsd: String(amountUsd),
148
+ };
149
+ }
150
+ //# sourceMappingURL=hyperliquid-activation.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hyperliquid-activation.mjs","sourceRoot":"","sources":["../../../src/strategy/relay/hyperliquid-activation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Hex } from '@metamask/utils';\nimport { createModuleLogger } from '@metamask/utils';\nimport { BigNumber } from 'bignumber.js';\n\nimport { HYPERCORE_USDC_DECIMALS } from '../../constants';\nimport { projectLogger } from '../../logger';\nimport type {\n QuoteRequest,\n TransactionPayControllerMessenger,\n} from '../../types';\nimport { getHyperliquidActivationFeeConfig } from '../../utils/feature-flags';\nimport { HYPERLIQUID_INFO_URL } from './constants';\n\nconst log = createModuleLogger(projectLogger, 'hyperliquid-activation');\n\n/**\n * HyperLiquid non-funding ledger `delta` types that represent an outbound\n * transfer initiated by the account (and therefore pay the activation fee).\n * A funding deposit can arrive as an inbound `send`, so direction is checked\n * for transfer types via the `user`/`destination` fields.\n */\nconst OUTBOUND_TRANSFER_TYPES = new Set(['send', 'spotTransfer']);\n\n/**\n * Minimal shape of a HyperLiquid `userNonFundingLedgerUpdates` entry. Only the\n * fields used to determine transfer direction are typed.\n */\nexport type HyperLiquidLedgerUpdate = {\n delta?: {\n type?: string;\n user?: string;\n destination?: string;\n };\n};\n\n/**\n * Whether a single ledger entry is an outbound action initiated by the account.\n *\n * @param update - The ledger entry.\n * @param normalizedAddress - The account's lowercased address.\n * @returns True when the account itself sent funds out.\n */\nfunction isOutboundFromAccount(\n update: HyperLiquidLedgerUpdate,\n normalizedAddress: string,\n): boolean {\n const delta = update?.delta;\n\n if (!delta?.type) {\n return false;\n }\n\n // Bridge withdrawals are always outbound from the account.\n if (delta.type === 'withdraw') {\n return true;\n }\n\n // A spot send/transfer counts only when this account is the sender and the\n // funds leave it (an inbound receipt has `user` set to the other party).\n if (OUTBOUND_TRANSFER_TYPES.has(delta.type)) {\n const sender = delta.user?.toLowerCase();\n const destination = delta.destination?.toLowerCase();\n return sender === normalizedAddress && destination !== normalizedAddress;\n }\n\n return false;\n}\n\n/**\n * Whether the HyperCore account has already paid the activation fee.\n *\n * The fee is charged on the account's first outbound send/withdrawal, so an\n * account that has only ever received deposits (or has no non-funding ledger\n * history) is treated as unactivated. Direction matters: only entries the\n * account itself initiated count.\n *\n * @param updates - Raw HyperLiquid non-funding ledger updates.\n * @param address - The account's address.\n * @returns True when the account has made a prior outbound transfer.\n */\nexport function isHyperLiquidAccountActivated(\n updates: HyperLiquidLedgerUpdate[],\n address: string,\n): boolean {\n if (!address) {\n return false;\n }\n\n const normalizedAddress = address.toLowerCase();\n\n return updates.some((update) =>\n isOutboundFromAccount(update, normalizedAddress),\n );\n}\n\n/**\n * Query HyperLiquid for the account's activation state.\n *\n * On any error (network failure, non-OK response, malformed body) the account\n * is treated as activated so the common path is never penalised by a transient\n * HyperLiquid outage; an unactivated account would then surface the original\n * HyperLiquid error, matching the pre-feature behaviour.\n *\n * @param address - The HyperCore account address.\n * @param signal - Optional abort signal forwarded to the request.\n * @returns True when the account is activated (or activation can't be determined).\n */\nasync function fetchIsAccountActivated(\n address: Hex,\n signal?: AbortSignal,\n): Promise<boolean> {\n try {\n const response = await fetch(HYPERLIQUID_INFO_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n type: 'userNonFundingLedgerUpdates',\n user: address,\n startTime: 0,\n }),\n signal,\n });\n\n if (!response.ok) {\n log('Activation check returned non-OK, assuming activated', {\n status: response.status,\n });\n return true;\n }\n\n const updates = (await response.json()) as HyperLiquidLedgerUpdate[];\n\n return isHyperLiquidAccountActivated(updates, address);\n } catch (error) {\n log('Activation check failed, assuming activated', { error });\n return true;\n }\n}\n\n/**\n * Reserve the one-time HyperLiquid activation fee for an unactivated HyperCore\n * source account.\n *\n * Reduces the amount sent to the provider so HyperLiquid retains enough balance\n * for the activation fee on the `sendAsset` step, and records the reserved fee\n * (USD) so it can be added to the provider fee — keeping the displayed\n * withdrawal amount unchanged.\n *\n * No-op for non-HyperLiquid sources, when the feature flag is disabled, when\n * the account is already activated, or when the amount is too small to reserve.\n *\n * @param request - Normalized quote request.\n * @param messenger - Controller messenger.\n * @param transactionType - Parent transaction type used to resolve the feature\n * flag override.\n * @param signal - Optional abort signal forwarded to the activation request.\n * @returns The (possibly adjusted) quote request.\n */\nexport async function applyHyperliquidActivationFee(\n request: QuoteRequest,\n messenger: TransactionPayControllerMessenger,\n transactionType?: string,\n signal?: AbortSignal,\n): Promise<QuoteRequest> {\n if (!request.isHyperliquidSource) {\n return request;\n }\n\n const { enabled, amountUsd } = getHyperliquidActivationFeeConfig(\n messenger,\n transactionType,\n );\n\n if (!enabled) {\n return request;\n }\n\n const activated = await fetchIsAccountActivated(request.from, signal);\n\n if (activated) {\n return request;\n }\n\n const feeRaw = new BigNumber(amountUsd).shiftedBy(HYPERCORE_USDC_DECIMALS);\n const reducedAmount = new BigNumber(request.sourceTokenAmount).minus(feeRaw);\n\n // Can't reserve more than the balance — let the original amount through so\n // the existing HyperLiquid error surfaces (withdrawable <= activation fee).\n if (reducedAmount.lte(0)) {\n log('Skipping activation reserve as amount is not greater than fee', {\n sourceTokenAmount: request.sourceTokenAmount,\n feeRaw: feeRaw.toFixed(0),\n });\n return request;\n }\n\n log('Reserving HyperLiquid activation fee', {\n amountUsd,\n originalSourceTokenAmount: request.sourceTokenAmount,\n reducedSourceTokenAmount: reducedAmount.toFixed(0),\n });\n\n return {\n ...request,\n sourceTokenAmount: reducedAmount.toFixed(0),\n hyperliquidActivationFeeUsd: String(amountUsd),\n };\n}\n"]}
@@ -17,6 +17,7 @@ const quote_gas_1 = require("../../utils/quote-gas.cjs");
17
17
  const token_1 = require("../../utils/token.cjs");
18
18
  const transaction_1 = require("../../utils/transaction.cjs");
19
19
  const constants_2 = require("./constants.cjs");
20
+ const hyperliquid_activation_1 = require("./hyperliquid-activation.cjs");
20
21
  const withdraw_1 = require("./polymarket/withdraw.cjs");
21
22
  const relay_api_1 = require("./relay-api.cjs");
22
23
  const relay_max_gas_station_1 = require("./relay-max-gas-station.cjs");
@@ -26,6 +27,7 @@ const log = (0, utils_1.createModuleLogger)(logger_1.projectLogger, 'relay-strat
26
27
  const POST_QUOTE_GAS_BUFFER = 1.1;
27
28
  // Hardcoded gas allowance for the prepended payment override transaction(s).
28
29
  const PAYMENT_OVERRIDE_GAS = 75000;
30
+ const ZERO_AMOUNT = { fiat: '0', human: '0', raw: '0', usd: '0' };
29
31
  /**
30
32
  * Fetches Relay quotes.
31
33
  *
@@ -36,7 +38,7 @@ async function getRelayQuotes(request) {
36
38
  const { requests } = request;
37
39
  log('Fetching quotes', requests);
38
40
  try {
39
- const normalizedRequests = requests
41
+ const normalizedRequests = await Promise.all(requests
40
42
  .filter((singleRequest) => {
41
43
  const hasTargetMinimum = singleRequest.targetAmountMinimum !== '0';
42
44
  const isPostQuote = Boolean(singleRequest.isPostQuote);
@@ -44,7 +46,8 @@ async function getRelayQuotes(request) {
44
46
  new bignumber_js_1.BigNumber(singleRequest.sourceTokenAmount).gt(0);
45
47
  return hasTargetMinimum || isPostQuote || isExactInputRequest;
46
48
  })
47
- .map((singleRequest) => normalizeRequest(singleRequest, request.transaction));
49
+ .map((singleRequest) => normalizeRequest(singleRequest, request.transaction))
50
+ .map((normalizedRequest) => (0, hyperliquid_activation_1.applyHyperliquidActivationFee)(normalizedRequest, request.messenger, request.transaction.type, request.signal)));
48
51
  log('Normalized requests', normalizedRequests);
49
52
  return await Promise.all(normalizedRequests.map((singleRequest) => getQuoteWithMaxAmountHandling(singleRequest, request)));
50
53
  }
@@ -356,11 +359,15 @@ async function normalizeQuote(quote, request, fullRequest) {
356
359
  const subsidizedFeeUsd = getSubsidizedFeeAmountUsd(quote);
357
360
  const appFeeUsd = new bignumber_js_1.BigNumber(quote.fees?.app?.amountUsd ?? '0');
358
361
  const metaMaskFee = (0, amounts_1.getFiatValueFromUsd)(appFeeUsd, usdToFiatRate);
359
- // Subtract app fee from provider fee since totalImpact.usd already includes it
360
- const providerFeeUsd = calculateProviderFee(quote).minus(appFeeUsd);
361
- const provider = subsidizedFeeUsd.gt(0)
362
- ? { usd: '0', fiat: '0' }
363
- : (0, amounts_1.getFiatValueFromUsd)(providerFeeUsd, usdToFiatRate);
362
+ // Subtract app fee from provider fee since totalImpact.usd already includes
363
+ // it. The relay provider fee is forced to zero when the quote is subsidized,
364
+ // but any reserved HyperLiquid activation fee is withheld from the source
365
+ // send regardless, so it must always be surfaced in the provider fee.
366
+ const activationFeeUsd = new bignumber_js_1.BigNumber(request.hyperliquidActivationFeeUsd ?? '0');
367
+ const providerFeeUsd = subsidizedFeeUsd.gt(0)
368
+ ? activationFeeUsd
369
+ : calculateProviderFee(quote).minus(appFeeUsd).plus(activationFeeUsd);
370
+ const provider = (0, amounts_1.getFiatValueFromUsd)(providerFeeUsd, usdToFiatRate);
364
371
  const { gasLimits, is7702, isGasFeeToken: isSourceGasFeeToken, ...sourceNetwork } = await calculateSourceNetworkCost(quote, messenger, request, fullRequest.transaction);
365
372
  const targetNetwork = {
366
373
  usd: '0',
@@ -466,10 +473,9 @@ async function calculateSourceNetworkCost(quote, messenger, request, transaction
466
473
  const { from, sourceChainId, sourceTokenAddress } = request;
467
474
  if (quote.metamask?.isExecute) {
468
475
  log('Zeroing network fees for execute flow');
469
- const zeroAmount = { fiat: '0', human: '0', raw: '0', usd: '0' };
470
476
  return {
471
- estimate: zeroAmount,
472
- max: zeroAmount,
477
+ estimate: ZERO_AMOUNT,
478
+ max: ZERO_AMOUNT,
473
479
  gasLimits: [],
474
480
  is7702: false,
475
481
  };
@@ -478,14 +484,26 @@ async function calculateSourceNetworkCost(quote, messenger, request, transaction
478
484
  // sendAsset (off-chain signature), not an on-chain transaction.
479
485
  if (request.isHyperliquidSource) {
480
486
  log('Zeroing network fees for HyperLiquid withdrawal (gasless)');
481
- const zeroAmount = { fiat: '0', human: '0', raw: '0', usd: '0' };
482
487
  return {
483
- estimate: zeroAmount,
484
- max: zeroAmount,
488
+ estimate: ZERO_AMOUNT,
489
+ max: ZERO_AMOUNT,
485
490
  gasLimits: [],
486
491
  is7702: false,
487
492
  };
488
493
  }
494
+ if (transaction.isGasFeeSponsored &&
495
+ request.sourceChainId === transaction.chainId &&
496
+ request.targetChainId === transaction.chainId) {
497
+ log('Zeroing source network fees for sponsored same-chain Relay route');
498
+ // Gas limit is zero as sponsored transactions go through the EIP-7702
499
+ // gas station hook and do not require user-paid gas.
500
+ return {
501
+ estimate: ZERO_AMOUNT,
502
+ max: ZERO_AMOUNT,
503
+ gasLimits: [0],
504
+ is7702: true,
505
+ };
506
+ }
489
507
  const txSteps = quote.steps.filter((step) => step.kind === 'transaction');
490
508
  const relayParams = txSteps
491
509
  .flatMap((step) => step.items)