@metamask/transaction-pay-controller 16.3.0 → 16.4.1

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 (148) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/dist/TransactionPayController.cjs +9 -0
  3. package/dist/TransactionPayController.cjs.map +1 -1
  4. package/dist/TransactionPayController.d.cts +2 -1
  5. package/dist/TransactionPayController.d.cts.map +1 -1
  6. package/dist/TransactionPayController.d.mts +2 -1
  7. package/dist/TransactionPayController.d.mts.map +1 -1
  8. package/dist/TransactionPayController.mjs +9 -0
  9. package/dist/TransactionPayController.mjs.map +1 -1
  10. package/dist/actions/update-fiat-payment.cjs +29 -0
  11. package/dist/actions/update-fiat-payment.cjs.map +1 -0
  12. package/dist/actions/update-fiat-payment.d.cts +14 -0
  13. package/dist/actions/update-fiat-payment.d.cts.map +1 -0
  14. package/dist/actions/update-fiat-payment.d.mts +14 -0
  15. package/dist/actions/update-fiat-payment.d.mts.map +1 -0
  16. package/dist/actions/update-fiat-payment.mjs +25 -0
  17. package/dist/actions/update-fiat-payment.mjs.map +1 -0
  18. package/dist/actions/update-payment-token.cjs +1 -0
  19. package/dist/actions/update-payment-token.cjs.map +1 -1
  20. package/dist/actions/update-payment-token.d.cts.map +1 -1
  21. package/dist/actions/update-payment-token.d.mts.map +1 -1
  22. package/dist/actions/update-payment-token.mjs +1 -0
  23. package/dist/actions/update-payment-token.mjs.map +1 -1
  24. package/dist/constants.cjs +1 -0
  25. package/dist/constants.cjs.map +1 -1
  26. package/dist/constants.d.cts +1 -0
  27. package/dist/constants.d.cts.map +1 -1
  28. package/dist/constants.d.mts +1 -0
  29. package/dist/constants.d.mts.map +1 -1
  30. package/dist/constants.mjs +1 -0
  31. package/dist/constants.mjs.map +1 -1
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +1 -1
  34. package/dist/index.d.cts.map +1 -1
  35. package/dist/index.d.mts +1 -1
  36. package/dist/index.d.mts.map +1 -1
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/strategy/across/AcrossStrategy.cjs +28 -0
  39. package/dist/strategy/across/AcrossStrategy.cjs.map +1 -0
  40. package/dist/strategy/across/AcrossStrategy.d.cts +8 -0
  41. package/dist/strategy/across/AcrossStrategy.d.cts.map +1 -0
  42. package/dist/strategy/across/AcrossStrategy.d.mts +8 -0
  43. package/dist/strategy/across/AcrossStrategy.d.mts.map +1 -0
  44. package/dist/strategy/across/AcrossStrategy.mjs +24 -0
  45. package/dist/strategy/across/AcrossStrategy.mjs.map +1 -0
  46. package/dist/strategy/across/across-quotes.cjs +356 -0
  47. package/dist/strategy/across/across-quotes.cjs.map +1 -0
  48. package/dist/strategy/across/across-quotes.d.cts +10 -0
  49. package/dist/strategy/across/across-quotes.d.cts.map +1 -0
  50. package/dist/strategy/across/across-quotes.d.mts +10 -0
  51. package/dist/strategy/across/across-quotes.d.mts.map +1 -0
  52. package/dist/strategy/across/across-quotes.mjs +352 -0
  53. package/dist/strategy/across/across-quotes.mjs.map +1 -0
  54. package/dist/strategy/across/across-submit.cjs +241 -0
  55. package/dist/strategy/across/across-submit.cjs.map +1 -0
  56. package/dist/strategy/across/across-submit.d.cts +13 -0
  57. package/dist/strategy/across/across-submit.d.cts.map +1 -0
  58. package/dist/strategy/across/across-submit.d.mts +13 -0
  59. package/dist/strategy/across/across-submit.d.mts.map +1 -0
  60. package/dist/strategy/across/across-submit.mjs +237 -0
  61. package/dist/strategy/across/across-submit.mjs.map +1 -0
  62. package/dist/strategy/across/types.cjs +3 -0
  63. package/dist/strategy/across/types.cjs.map +1 -0
  64. package/dist/strategy/across/types.d.cts +89 -0
  65. package/dist/strategy/across/types.d.cts.map +1 -0
  66. package/dist/strategy/across/types.d.mts +89 -0
  67. package/dist/strategy/across/types.d.mts.map +1 -0
  68. package/dist/strategy/across/types.mjs +2 -0
  69. package/dist/strategy/across/types.mjs.map +1 -0
  70. package/dist/strategy/relay/RelayStrategy.cjs +5 -0
  71. package/dist/strategy/relay/RelayStrategy.cjs.map +1 -1
  72. package/dist/strategy/relay/RelayStrategy.d.cts +1 -0
  73. package/dist/strategy/relay/RelayStrategy.d.cts.map +1 -1
  74. package/dist/strategy/relay/RelayStrategy.d.mts +1 -0
  75. package/dist/strategy/relay/RelayStrategy.d.mts.map +1 -1
  76. package/dist/strategy/relay/RelayStrategy.mjs +5 -0
  77. package/dist/strategy/relay/RelayStrategy.mjs.map +1 -1
  78. package/dist/strategy/relay/gas-station.cjs +92 -0
  79. package/dist/strategy/relay/gas-station.cjs.map +1 -0
  80. package/dist/strategy/relay/gas-station.d.cts +22 -0
  81. package/dist/strategy/relay/gas-station.d.cts.map +1 -0
  82. package/dist/strategy/relay/gas-station.d.mts +22 -0
  83. package/dist/strategy/relay/gas-station.d.mts.map +1 -0
  84. package/dist/strategy/relay/gas-station.mjs +87 -0
  85. package/dist/strategy/relay/gas-station.mjs.map +1 -0
  86. package/dist/strategy/relay/relay-max-gas-station.cjs +242 -0
  87. package/dist/strategy/relay/relay-max-gas-station.cjs.map +1 -0
  88. package/dist/strategy/relay/relay-max-gas-station.d.cts +22 -0
  89. package/dist/strategy/relay/relay-max-gas-station.d.cts.map +1 -0
  90. package/dist/strategy/relay/relay-max-gas-station.d.mts +22 -0
  91. package/dist/strategy/relay/relay-max-gas-station.d.mts.map +1 -0
  92. package/dist/strategy/relay/relay-max-gas-station.mjs +238 -0
  93. package/dist/strategy/relay/relay-max-gas-station.mjs.map +1 -0
  94. package/dist/strategy/relay/relay-quotes.cjs +32 -59
  95. package/dist/strategy/relay/relay-quotes.cjs.map +1 -1
  96. package/dist/strategy/relay/relay-quotes.d.cts.map +1 -1
  97. package/dist/strategy/relay/relay-quotes.d.mts.map +1 -1
  98. package/dist/strategy/relay/relay-quotes.mjs +29 -56
  99. package/dist/strategy/relay/relay-quotes.mjs.map +1 -1
  100. package/dist/strategy/relay/types.cjs.map +1 -1
  101. package/dist/strategy/relay/types.d.cts +1 -0
  102. package/dist/strategy/relay/types.d.cts.map +1 -1
  103. package/dist/strategy/relay/types.d.mts +1 -0
  104. package/dist/strategy/relay/types.d.mts.map +1 -1
  105. package/dist/strategy/relay/types.mjs.map +1 -1
  106. package/dist/types.cjs.map +1 -1
  107. package/dist/types.d.cts +24 -1
  108. package/dist/types.d.cts.map +1 -1
  109. package/dist/types.d.mts +24 -1
  110. package/dist/types.d.mts.map +1 -1
  111. package/dist/types.mjs.map +1 -1
  112. package/dist/utils/amounts.cjs +40 -0
  113. package/dist/utils/amounts.cjs.map +1 -0
  114. package/dist/utils/amounts.d.cts +18 -0
  115. package/dist/utils/amounts.d.cts.map +1 -0
  116. package/dist/utils/amounts.d.mts +18 -0
  117. package/dist/utils/amounts.d.mts.map +1 -0
  118. package/dist/utils/amounts.mjs +35 -0
  119. package/dist/utils/amounts.mjs.map +1 -0
  120. package/dist/utils/feature-flags.cjs +45 -6
  121. package/dist/utils/feature-flags.cjs.map +1 -1
  122. package/dist/utils/feature-flags.d.cts +45 -2
  123. package/dist/utils/feature-flags.d.cts.map +1 -1
  124. package/dist/utils/feature-flags.d.mts +45 -2
  125. package/dist/utils/feature-flags.d.mts.map +1 -1
  126. package/dist/utils/feature-flags.mjs +42 -5
  127. package/dist/utils/feature-flags.mjs.map +1 -1
  128. package/dist/utils/gas.cjs +46 -1
  129. package/dist/utils/gas.cjs.map +1 -1
  130. package/dist/utils/gas.d.cts +18 -0
  131. package/dist/utils/gas.d.cts.map +1 -1
  132. package/dist/utils/gas.d.mts +18 -0
  133. package/dist/utils/gas.d.mts.map +1 -1
  134. package/dist/utils/gas.mjs +44 -0
  135. package/dist/utils/gas.mjs.map +1 -1
  136. package/dist/utils/strategy.cjs +3 -0
  137. package/dist/utils/strategy.cjs.map +1 -1
  138. package/dist/utils/strategy.d.cts.map +1 -1
  139. package/dist/utils/strategy.d.mts.map +1 -1
  140. package/dist/utils/strategy.mjs +3 -0
  141. package/dist/utils/strategy.mjs.map +1 -1
  142. package/dist/utils/totals.cjs +4 -19
  143. package/dist/utils/totals.cjs.map +1 -1
  144. package/dist/utils/totals.d.cts.map +1 -1
  145. package/dist/utils/totals.d.mts.map +1 -1
  146. package/dist/utils/totals.mjs +1 -16
  147. package/dist/utils/totals.mjs.map +1 -1
  148. package/package.json +5 -5
@@ -0,0 +1,238 @@
1
+ import { createModuleLogger } from "@metamask/utils";
2
+ import { BigNumber } from "bignumber.js";
3
+ import { getGasStationEligibility, getGasStationCostInSourceTokenRaw } from "./gas-station.mjs";
4
+ import { projectLogger } from "../../logger.mjs";
5
+ import { getNativeToken, getTokenBalance, getTokenInfo } from "../../utils/token.mjs";
6
+ const log = createModuleLogger(projectLogger, 'relay-max-gas-station');
7
+ const PROBE_AMOUNT_PERCENTAGE = 0.25;
8
+ var GasCostEstimateSource;
9
+ (function (GasCostEstimateSource) {
10
+ GasCostEstimateSource["GasStation"] = "gas-station";
11
+ GasCostEstimateSource["Probe"] = "probe";
12
+ GasCostEstimateSource["Quote"] = "quote";
13
+ })(GasCostEstimateSource || (GasCostEstimateSource = {}));
14
+ /**
15
+ * Returns a Relay max-amount quote using a two-phase gas-station fallback.
16
+ *
17
+ * It first requests a standard max quote (phase 1), then when needed estimates
18
+ * gas in source-token units (directly or via a probe quote), requests an
19
+ * adjusted max quote (phase 2), and accepts phase 2 only if validation passes
20
+ * (source gas fee token selected, gas-limit checks, affordability).
21
+ *
22
+ * If any step fails or validation is unsafe, it safely falls back to phase 1.
23
+ * Successful phase-2 quotes are tagged with `metamask.isMaxGasStation = true`.
24
+ *
25
+ * @param request - Relay quote request for a max-amount flow.
26
+ * @param fullRequest - Full quote request context including messenger and transaction.
27
+ * @param getSingleQuote - Quote fetcher used for phase-1, phase-2, and probe quotes.
28
+ * @returns The validated adjusted phase-2 quote, or the original phase-1 quote on fallback.
29
+ */
30
+ export async function getRelayMaxGasStationQuote(request, fullRequest, getSingleQuote) {
31
+ const { messenger } = fullRequest;
32
+ const { sourceChainId, sourceTokenAmount } = request;
33
+ const context = {
34
+ fullRequest,
35
+ getSingleQuote,
36
+ messenger,
37
+ request,
38
+ };
39
+ const phase1Quote = await getSingleQuote(request, fullRequest);
40
+ const nativeBalanceCheck = checkEnoughNativeBalanceIfSourceGasFeeTokenNotUsed(phase1Quote, messenger, request);
41
+ if (nativeBalanceCheck.hasEnoughNativeBalance) {
42
+ return fallbackToPhase1(phase1Quote, 'Native balance is sufficient for gas', {
43
+ nativeBalance: nativeBalanceCheck.nativeBalance,
44
+ nativeGasCost: nativeBalanceCheck.nativeGasCostRaw,
45
+ });
46
+ }
47
+ const gasStationEligibility = getGasStationEligibility(messenger, sourceChainId);
48
+ if (!gasStationEligibility.isEligible) {
49
+ return fallbackToPhase1(phase1Quote, 'Gas station is disabled or unsupported');
50
+ }
51
+ const sourceAmountBN = new BigNumber(sourceTokenAmount);
52
+ const probeCost = await getProbeGasCostInSourceTokenRaw(context);
53
+ if (!probeCost) {
54
+ return fallbackToPhase1(phase1Quote, 'Unable to estimate gas-station source token cost');
55
+ }
56
+ const initialGasEstimate = {
57
+ amount: probeCost,
58
+ source: GasCostEstimateSource.Probe,
59
+ };
60
+ const adjustedSourceAmount = getAdjustedSourceAmount(sourceAmountBN, initialGasEstimate.amount);
61
+ if (!adjustedSourceAmount.isGreaterThan(0)) {
62
+ return fallbackToPhase1(phase1Quote, 'Insufficient balance for gas station after max adjustment');
63
+ }
64
+ const phase2Quote = await getAdjustedPhase2Quote(adjustedSourceAmount, initialGasEstimate, context);
65
+ if (!phase2Quote) {
66
+ return fallbackToPhase1(phase1Quote, 'Adjusted phase-2 quote request failed');
67
+ }
68
+ if (!phase2Quote.fees.isSourceGasFeeToken) {
69
+ return fallbackToPhase1(phase1Quote, 'Adjusted quote did not return source gas fee token pricing');
70
+ }
71
+ const phase1GasLimits = phase1Quote.original.metamask.gasLimits ?? [];
72
+ const phase2GasLimits = phase2Quote.original.metamask.gasLimits ?? [];
73
+ if (!hasMatchingGasLimitShape(phase1GasLimits, phase2GasLimits)) {
74
+ return fallbackToPhase1(phase1Quote, 'Adjusted quote gas limit shape changed between phases', {
75
+ phase1GasLimits,
76
+ phase2GasLimits,
77
+ });
78
+ }
79
+ const phase1TotalGasLimit = getTotalGasLimit(phase1GasLimits);
80
+ const phase2TotalGasLimit = getTotalGasLimit(phase2GasLimits);
81
+ if (phase2TotalGasLimit > phase1TotalGasLimit) {
82
+ return fallbackToPhase1(phase1Quote, 'Adjusted quote total gas limit increased between phases', {
83
+ phase1TotalGasLimit,
84
+ phase2TotalGasLimit,
85
+ });
86
+ }
87
+ const validationGasEstimate = new BigNumber(phase2Quote.fees.sourceNetwork.max.raw);
88
+ if (!isAdjustedAmountAffordable(adjustedSourceAmount, validationGasEstimate, sourceAmountBN)) {
89
+ return fallbackToPhase1(phase1Quote, 'Adjusted quote fails affordability validation', {
90
+ adjustedSourceAmount: adjustedSourceAmount.toString(10),
91
+ validationGasCost: validationGasEstimate.toString(10),
92
+ });
93
+ }
94
+ markQuoteAsMaxGasStation(phase2Quote);
95
+ return phase2Quote;
96
+ }
97
+ function checkEnoughNativeBalanceIfSourceGasFeeTokenNotUsed(quote, messenger, request) {
98
+ if (quote.fees.isSourceGasFeeToken) {
99
+ return { hasEnoughNativeBalance: false };
100
+ }
101
+ const nativeGasCostRaw = quote.fees.sourceNetwork.max.raw;
102
+ const nativeBalance = getTokenBalance(messenger, request.from, request.sourceChainId, getNativeToken(request.sourceChainId));
103
+ return {
104
+ hasEnoughNativeBalance: new BigNumber(nativeBalance).isGreaterThanOrEqualTo(nativeGasCostRaw),
105
+ nativeBalance,
106
+ nativeGasCostRaw,
107
+ };
108
+ }
109
+ function getAdjustedSourceAmount(sourceAmount, estimatedGasCost) {
110
+ return sourceAmount
111
+ .minus(estimatedGasCost)
112
+ .integerValue(BigNumber.ROUND_DOWN);
113
+ }
114
+ function isAdjustedAmountAffordable(adjustedSourceAmount, gasCost, originalSourceAmount) {
115
+ return adjustedSourceAmount
116
+ .plus(gasCost)
117
+ .isLessThanOrEqualTo(originalSourceAmount);
118
+ }
119
+ async function getAdjustedPhase2Quote(adjustedSourceAmount, initialGasEstimate, context) {
120
+ const { fullRequest, getSingleQuote, request } = context;
121
+ log('Requesting adjusted max quote', {
122
+ adjustedAmount: adjustedSourceAmount.toString(10),
123
+ gasCostInSourceToken: initialGasEstimate.amount.toString(10),
124
+ gasEstimateSource: initialGasEstimate.source,
125
+ originalAmount: request.sourceTokenAmount,
126
+ });
127
+ try {
128
+ return await getSingleQuote({
129
+ ...request,
130
+ sourceTokenAmount: adjustedSourceAmount.toFixed(0, BigNumber.ROUND_DOWN),
131
+ }, fullRequest);
132
+ }
133
+ catch (error) {
134
+ log('Adjusted quote request failed, falling back to phase-1 quote', {
135
+ error,
136
+ });
137
+ return undefined;
138
+ }
139
+ }
140
+ async function getGasCostFromQuoteOrGasStation(quote, messenger, request) {
141
+ const gasCost = quote.fees.sourceNetwork.max;
142
+ if (quote.fees.isSourceGasFeeToken) {
143
+ log('Gas cost already in source token units', { raw: gasCost.raw });
144
+ return {
145
+ amount: new BigNumber(gasCost.raw),
146
+ source: GasCostEstimateSource.Quote,
147
+ };
148
+ }
149
+ const firstStepData = quote.original.steps[0]?.items[0]?.data;
150
+ if (!firstStepData) {
151
+ return undefined;
152
+ }
153
+ const totalItemCount = quote.original.steps.reduce((count, step) => count + step.items.length, 0);
154
+ const totalGasEstimate = (quote.original.metamask.gasLimits ?? []).reduce((acc, gasLimit) => acc + gasLimit, 0);
155
+ const gasStationCost = await getGasStationCostInSourceTokenRaw({
156
+ firstStepData,
157
+ messenger,
158
+ request: {
159
+ from: request.from,
160
+ sourceChainId: request.sourceChainId,
161
+ sourceTokenAddress: request.sourceTokenAddress,
162
+ },
163
+ totalGasEstimate,
164
+ totalItemCount,
165
+ });
166
+ if (!gasStationCost) {
167
+ return undefined;
168
+ }
169
+ return {
170
+ amount: new BigNumber(gasStationCost.raw),
171
+ source: GasCostEstimateSource.GasStation,
172
+ };
173
+ }
174
+ async function getProbeGasCostInSourceTokenRaw(context) {
175
+ const { fullRequest, getSingleQuote, messenger, request } = context;
176
+ const sourceTokenInfo = getTokenInfo(messenger, request.sourceTokenAddress, request.sourceChainId);
177
+ if (!sourceTokenInfo) {
178
+ return undefined;
179
+ }
180
+ const probeAmount = getProbeSourceAmountRaw(request.sourceTokenAmount, sourceTokenInfo.decimals);
181
+ if (!probeAmount || probeAmount === request.sourceTokenAmount) {
182
+ return undefined;
183
+ }
184
+ log('Requesting probe quote for gas station estimation', {
185
+ originalSourceAmount: request.sourceTokenAmount,
186
+ probeAmount,
187
+ });
188
+ let probeQuote;
189
+ try {
190
+ probeQuote = await getSingleQuote({
191
+ ...request,
192
+ sourceTokenAmount: probeAmount,
193
+ }, fullRequest);
194
+ }
195
+ catch (error) {
196
+ log('Probe quote request failed', { error });
197
+ return undefined;
198
+ }
199
+ if (!probeQuote) {
200
+ return undefined;
201
+ }
202
+ const probeEstimate = await getGasCostFromQuoteOrGasStation(probeQuote, messenger, request);
203
+ return probeEstimate?.amount;
204
+ }
205
+ function getProbeSourceAmountRaw(sourceAmountRaw, sourceDecimals) {
206
+ const sourceAmount = new BigNumber(sourceAmountRaw);
207
+ if (sourceAmount.isLessThanOrEqualTo(0)) {
208
+ return undefined;
209
+ }
210
+ const probeRawAmount = sourceAmount
211
+ .multipliedBy(PROBE_AMOUNT_PERCENTAGE)
212
+ .integerValue(BigNumber.ROUND_FLOOR);
213
+ // Minimum probe size: ~0.01 token for tokens with >=2 decimals,
214
+ // otherwise one raw unit for low-decimal tokens.
215
+ const minimumProbeRaw = new BigNumber(1).shiftedBy(Math.max(sourceDecimals - 2, 0));
216
+ const probeRaw = BigNumber.minimum(sourceAmount, BigNumber.maximum(probeRawAmount, minimumProbeRaw)).integerValue(BigNumber.ROUND_FLOOR);
217
+ if (probeRaw.isLessThanOrEqualTo(0)) {
218
+ return undefined;
219
+ }
220
+ return probeRaw.toFixed(0);
221
+ }
222
+ function fallbackToPhase1(phase1Quote, message, paramsToLog) {
223
+ log(message, paramsToLog);
224
+ return phase1Quote;
225
+ }
226
+ function hasMatchingGasLimitShape(phase1, phase2) {
227
+ return phase1.length === phase2.length;
228
+ }
229
+ function getTotalGasLimit(gasLimits) {
230
+ return gasLimits.reduce((total, gasLimit) => total + gasLimit, 0);
231
+ }
232
+ function markQuoteAsMaxGasStation(quote) {
233
+ quote.original.metamask = {
234
+ ...quote.original.metamask,
235
+ isMaxGasStation: true,
236
+ };
237
+ }
238
+ //# sourceMappingURL=relay-max-gas-station.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay-max-gas-station.mjs","sourceRoot":"","sources":["../../../src/strategy/relay/relay-max-gas-station.ts"],"names":[],"mappings":"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","sourcesContent":["import { createModuleLogger } from '@metamask/utils';\nimport { BigNumber } from 'bignumber.js';\n\nimport {\n getGasStationEligibility,\n getGasStationCostInSourceTokenRaw,\n} from './gas-station';\nimport type { RelayQuote } from './types';\nimport { projectLogger } from '../../logger';\nimport type {\n PayStrategyGetQuotesRequest,\n QuoteRequest,\n TransactionPayControllerMessenger,\n TransactionPayQuote,\n} from '../../types';\nimport {\n getNativeToken,\n getTokenBalance,\n getTokenInfo,\n} from '../../utils/token';\n\nconst log = createModuleLogger(projectLogger, 'relay-max-gas-station');\n\nconst PROBE_AMOUNT_PERCENTAGE = 0.25;\n\nenum GasCostEstimateSource {\n GasStation = 'gas-station',\n Probe = 'probe',\n Quote = 'quote',\n}\n\ntype GasCostEstimate = {\n amount: BigNumber;\n source: GasCostEstimateSource;\n};\n\ntype NativeBalanceCheckResult = {\n hasEnoughNativeBalance: boolean;\n nativeBalance?: string;\n nativeGasCostRaw?: string;\n};\n\ntype GetSingleQuoteFn = (\n request: QuoteRequest,\n fullRequest: PayStrategyGetQuotesRequest,\n) => Promise<TransactionPayQuote<RelayQuote>>;\n\ntype MaxAmountQuoteContext = {\n fullRequest: PayStrategyGetQuotesRequest;\n getSingleQuote: GetSingleQuoteFn;\n messenger: TransactionPayControllerMessenger;\n request: QuoteRequest;\n};\n\n/**\n * Returns a Relay max-amount quote using a two-phase gas-station fallback.\n *\n * It first requests a standard max quote (phase 1), then when needed estimates\n * gas in source-token units (directly or via a probe quote), requests an\n * adjusted max quote (phase 2), and accepts phase 2 only if validation passes\n * (source gas fee token selected, gas-limit checks, affordability).\n *\n * If any step fails or validation is unsafe, it safely falls back to phase 1.\n * Successful phase-2 quotes are tagged with `metamask.isMaxGasStation = true`.\n *\n * @param request - Relay quote request for a max-amount flow.\n * @param fullRequest - Full quote request context including messenger and transaction.\n * @param getSingleQuote - Quote fetcher used for phase-1, phase-2, and probe quotes.\n * @returns The validated adjusted phase-2 quote, or the original phase-1 quote on fallback.\n */\nexport async function getRelayMaxGasStationQuote(\n request: QuoteRequest,\n fullRequest: PayStrategyGetQuotesRequest,\n getSingleQuote: GetSingleQuoteFn,\n): Promise<TransactionPayQuote<RelayQuote>> {\n const { messenger } = fullRequest;\n const { sourceChainId, sourceTokenAmount } = request;\n const context: MaxAmountQuoteContext = {\n fullRequest,\n getSingleQuote,\n messenger,\n request,\n };\n\n const phase1Quote = await getSingleQuote(request, fullRequest);\n\n const nativeBalanceCheck = checkEnoughNativeBalanceIfSourceGasFeeTokenNotUsed(\n phase1Quote,\n messenger,\n request,\n );\n\n if (nativeBalanceCheck.hasEnoughNativeBalance) {\n return fallbackToPhase1(\n phase1Quote,\n 'Native balance is sufficient for gas',\n {\n nativeBalance: nativeBalanceCheck.nativeBalance,\n nativeGasCost: nativeBalanceCheck.nativeGasCostRaw,\n },\n );\n }\n\n const gasStationEligibility = getGasStationEligibility(\n messenger,\n sourceChainId,\n );\n\n if (!gasStationEligibility.isEligible) {\n return fallbackToPhase1(\n phase1Quote,\n 'Gas station is disabled or unsupported',\n );\n }\n\n const sourceAmountBN = new BigNumber(sourceTokenAmount);\n const probeCost = await getProbeGasCostInSourceTokenRaw(context);\n\n if (!probeCost) {\n return fallbackToPhase1(\n phase1Quote,\n 'Unable to estimate gas-station source token cost',\n );\n }\n\n const initialGasEstimate: GasCostEstimate = {\n amount: probeCost,\n source: GasCostEstimateSource.Probe,\n };\n\n const adjustedSourceAmount = getAdjustedSourceAmount(\n sourceAmountBN,\n initialGasEstimate.amount,\n );\n\n if (!adjustedSourceAmount.isGreaterThan(0)) {\n return fallbackToPhase1(\n phase1Quote,\n 'Insufficient balance for gas station after max adjustment',\n );\n }\n\n const phase2Quote = await getAdjustedPhase2Quote(\n adjustedSourceAmount,\n initialGasEstimate,\n context,\n );\n\n if (!phase2Quote) {\n return fallbackToPhase1(\n phase1Quote,\n 'Adjusted phase-2 quote request failed',\n );\n }\n\n if (!phase2Quote.fees.isSourceGasFeeToken) {\n return fallbackToPhase1(\n phase1Quote,\n 'Adjusted quote did not return source gas fee token pricing',\n );\n }\n\n const phase1GasLimits = phase1Quote.original.metamask.gasLimits ?? [];\n const phase2GasLimits = phase2Quote.original.metamask.gasLimits ?? [];\n\n if (!hasMatchingGasLimitShape(phase1GasLimits, phase2GasLimits)) {\n return fallbackToPhase1(\n phase1Quote,\n 'Adjusted quote gas limit shape changed between phases',\n {\n phase1GasLimits,\n phase2GasLimits,\n },\n );\n }\n\n const phase1TotalGasLimit = getTotalGasLimit(phase1GasLimits);\n const phase2TotalGasLimit = getTotalGasLimit(phase2GasLimits);\n\n if (phase2TotalGasLimit > phase1TotalGasLimit) {\n return fallbackToPhase1(\n phase1Quote,\n 'Adjusted quote total gas limit increased between phases',\n {\n phase1TotalGasLimit,\n phase2TotalGasLimit,\n },\n );\n }\n\n const validationGasEstimate = new BigNumber(\n phase2Quote.fees.sourceNetwork.max.raw,\n );\n\n if (\n !isAdjustedAmountAffordable(\n adjustedSourceAmount,\n validationGasEstimate,\n sourceAmountBN,\n )\n ) {\n return fallbackToPhase1(\n phase1Quote,\n 'Adjusted quote fails affordability validation',\n {\n adjustedSourceAmount: adjustedSourceAmount.toString(10),\n validationGasCost: validationGasEstimate.toString(10),\n },\n );\n }\n\n markQuoteAsMaxGasStation(phase2Quote);\n\n return phase2Quote;\n}\n\nfunction checkEnoughNativeBalanceIfSourceGasFeeTokenNotUsed(\n quote: TransactionPayQuote<RelayQuote>,\n messenger: TransactionPayControllerMessenger,\n request: QuoteRequest,\n): NativeBalanceCheckResult {\n if (quote.fees.isSourceGasFeeToken) {\n return { hasEnoughNativeBalance: false };\n }\n\n const nativeGasCostRaw = quote.fees.sourceNetwork.max.raw;\n const nativeBalance = getTokenBalance(\n messenger,\n request.from,\n request.sourceChainId,\n getNativeToken(request.sourceChainId),\n );\n\n return {\n hasEnoughNativeBalance: new BigNumber(nativeBalance).isGreaterThanOrEqualTo(\n nativeGasCostRaw,\n ),\n nativeBalance,\n nativeGasCostRaw,\n };\n}\n\nfunction getAdjustedSourceAmount(\n sourceAmount: BigNumber,\n estimatedGasCost: BigNumber,\n): BigNumber {\n return sourceAmount\n .minus(estimatedGasCost)\n .integerValue(BigNumber.ROUND_DOWN);\n}\n\nfunction isAdjustedAmountAffordable(\n adjustedSourceAmount: BigNumber,\n gasCost: BigNumber,\n originalSourceAmount: BigNumber,\n): boolean {\n return adjustedSourceAmount\n .plus(gasCost)\n .isLessThanOrEqualTo(originalSourceAmount);\n}\n\nasync function getAdjustedPhase2Quote(\n adjustedSourceAmount: BigNumber,\n initialGasEstimate: GasCostEstimate,\n context: MaxAmountQuoteContext,\n): Promise<TransactionPayQuote<RelayQuote> | undefined> {\n const { fullRequest, getSingleQuote, request } = context;\n\n log('Requesting adjusted max quote', {\n adjustedAmount: adjustedSourceAmount.toString(10),\n gasCostInSourceToken: initialGasEstimate.amount.toString(10),\n gasEstimateSource: initialGasEstimate.source,\n originalAmount: request.sourceTokenAmount,\n });\n\n try {\n return await getSingleQuote(\n {\n ...request,\n sourceTokenAmount: adjustedSourceAmount.toFixed(\n 0,\n BigNumber.ROUND_DOWN,\n ),\n },\n fullRequest,\n );\n } catch (error) {\n log('Adjusted quote request failed, falling back to phase-1 quote', {\n error,\n });\n return undefined;\n }\n}\n\nasync function getGasCostFromQuoteOrGasStation(\n quote: TransactionPayQuote<RelayQuote>,\n messenger: TransactionPayControllerMessenger,\n request: QuoteRequest,\n): Promise<GasCostEstimate | undefined> {\n const gasCost = quote.fees.sourceNetwork.max;\n\n if (quote.fees.isSourceGasFeeToken) {\n log('Gas cost already in source token units', { raw: gasCost.raw });\n return {\n amount: new BigNumber(gasCost.raw),\n source: GasCostEstimateSource.Quote,\n };\n }\n\n const firstStepData = quote.original.steps[0]?.items[0]?.data;\n\n if (!firstStepData) {\n return undefined;\n }\n\n const totalItemCount = quote.original.steps.reduce(\n (count, step) => count + step.items.length,\n 0,\n );\n\n const totalGasEstimate = (quote.original.metamask.gasLimits ?? []).reduce(\n (acc, gasLimit) => acc + gasLimit,\n 0,\n );\n\n const gasStationCost = await getGasStationCostInSourceTokenRaw({\n firstStepData,\n messenger,\n request: {\n from: request.from,\n sourceChainId: request.sourceChainId,\n sourceTokenAddress: request.sourceTokenAddress,\n },\n totalGasEstimate,\n totalItemCount,\n });\n\n if (!gasStationCost) {\n return undefined;\n }\n\n return {\n amount: new BigNumber(gasStationCost.raw),\n source: GasCostEstimateSource.GasStation,\n };\n}\n\nasync function getProbeGasCostInSourceTokenRaw(\n context: MaxAmountQuoteContext,\n): Promise<BigNumber | undefined> {\n const { fullRequest, getSingleQuote, messenger, request } = context;\n\n const sourceTokenInfo = getTokenInfo(\n messenger,\n request.sourceTokenAddress,\n request.sourceChainId,\n );\n\n if (!sourceTokenInfo) {\n return undefined;\n }\n\n const probeAmount = getProbeSourceAmountRaw(\n request.sourceTokenAmount,\n sourceTokenInfo.decimals,\n );\n\n if (!probeAmount || probeAmount === request.sourceTokenAmount) {\n return undefined;\n }\n\n log('Requesting probe quote for gas station estimation', {\n originalSourceAmount: request.sourceTokenAmount,\n probeAmount,\n });\n\n let probeQuote: TransactionPayQuote<RelayQuote> | undefined;\n\n try {\n probeQuote = await getSingleQuote(\n {\n ...request,\n sourceTokenAmount: probeAmount,\n },\n fullRequest,\n );\n } catch (error) {\n log('Probe quote request failed', { error });\n return undefined;\n }\n\n if (!probeQuote) {\n return undefined;\n }\n\n const probeEstimate = await getGasCostFromQuoteOrGasStation(\n probeQuote,\n messenger,\n request,\n );\n\n return probeEstimate?.amount;\n}\n\nfunction getProbeSourceAmountRaw(\n sourceAmountRaw: string,\n sourceDecimals: number,\n): string | undefined {\n const sourceAmount = new BigNumber(sourceAmountRaw);\n\n if (sourceAmount.isLessThanOrEqualTo(0)) {\n return undefined;\n }\n\n const probeRawAmount = sourceAmount\n .multipliedBy(PROBE_AMOUNT_PERCENTAGE)\n .integerValue(BigNumber.ROUND_FLOOR);\n\n // Minimum probe size: ~0.01 token for tokens with >=2 decimals,\n // otherwise one raw unit for low-decimal tokens.\n const minimumProbeRaw = new BigNumber(1).shiftedBy(\n Math.max(sourceDecimals - 2, 0),\n );\n\n const probeRaw = BigNumber.minimum(\n sourceAmount,\n BigNumber.maximum(probeRawAmount, minimumProbeRaw),\n ).integerValue(BigNumber.ROUND_FLOOR);\n\n if (probeRaw.isLessThanOrEqualTo(0)) {\n return undefined;\n }\n\n return probeRaw.toFixed(0);\n}\n\nfunction fallbackToPhase1(\n phase1Quote: TransactionPayQuote<RelayQuote>,\n message: string,\n paramsToLog?: Record<string, unknown>,\n): TransactionPayQuote<RelayQuote> {\n log(message, paramsToLog);\n return phase1Quote;\n}\n\nfunction hasMatchingGasLimitShape(phase1: number[], phase2: number[]): boolean {\n return phase1.length === phase2.length;\n}\n\nfunction getTotalGasLimit(gasLimits: number[]): number {\n return gasLimits.reduce((total, gasLimit) => total + gasLimit, 0);\n}\n\nfunction markQuoteAsMaxGasStation(\n quote: TransactionPayQuote<RelayQuote>,\n): void {\n quote.original.metamask = {\n ...quote.original.metamask,\n isMaxGasStation: true,\n };\n}\n"]}
@@ -7,9 +7,12 @@ const controller_utils_1 = require("@metamask/controller-utils");
7
7
  const utils_1 = require("@metamask/utils");
8
8
  const bignumber_js_1 = require("bignumber.js");
9
9
  const constants_1 = require("./constants.cjs");
10
+ const gas_station_1 = require("./gas-station.cjs");
11
+ const relay_max_gas_station_1 = require("./relay-max-gas-station.cjs");
10
12
  const __1 = require("../../index.cjs");
11
13
  const constants_2 = require("../../constants.cjs");
12
14
  const logger_1 = require("../../logger.cjs");
15
+ const amounts_1 = require("../../utils/amounts.cjs");
13
16
  const feature_flags_1 = require("../../utils/feature-flags.cjs");
14
17
  const gas_1 = require("../../utils/gas.cjs");
15
18
  const token_1 = require("../../utils/token.cjs");
@@ -31,7 +34,7 @@ async function getRelayQuotes(request) {
31
34
  singleRequest.isPostQuote)
32
35
  .map((singleRequest) => normalizeRequest(singleRequest));
33
36
  log('Normalized requests', normalizedRequests);
34
- return await Promise.all(normalizedRequests.map((singleRequest) => getSingleQuote(singleRequest, request)));
37
+ return await Promise.all(normalizedRequests.map((singleRequest) => getQuoteWithMaxAmountHandling(singleRequest, request)));
35
38
  }
36
39
  catch (error) {
37
40
  log('Error fetching quotes', { error });
@@ -39,6 +42,13 @@ async function getRelayQuotes(request) {
39
42
  }
40
43
  }
41
44
  exports.getRelayQuotes = getRelayQuotes;
45
+ async function getQuoteWithMaxAmountHandling(request, fullRequest) {
46
+ const { isMaxAmount } = request;
47
+ if (!isMaxAmount) {
48
+ return getSingleQuote(request, fullRequest);
49
+ }
50
+ return (0, relay_max_gas_station_1.getRelayMaxGasStationQuote)(request, fullRequest, getSingleQuote);
51
+ }
42
52
  /**
43
53
  * Fetches a single Relay quote.
44
54
  *
@@ -197,15 +207,15 @@ async function normalizeQuote(quote, request, fullRequest) {
197
207
  const { details } = quote;
198
208
  const { currencyIn, currencyOut } = details;
199
209
  const { usdToFiatRate } = getFiatRates(messenger, request);
200
- const dust = getFiatValueFromUsd(calculateDustUsd(quote, request), usdToFiatRate);
210
+ const dust = (0, amounts_1.getFiatValueFromUsd)(calculateDustUsd(quote, request), usdToFiatRate);
201
211
  const subsidizedFeeUsd = getSubsidizedFeeAmountUsd(quote);
202
212
  const appFeeUsd = new bignumber_js_1.BigNumber(quote.fees?.app?.amountUsd ?? '0');
203
- const metaMaskFee = getFiatValueFromUsd(appFeeUsd, usdToFiatRate);
213
+ const metaMaskFee = (0, amounts_1.getFiatValueFromUsd)(appFeeUsd, usdToFiatRate);
204
214
  // Subtract app fee from provider fee since totalImpact.usd already includes it
205
215
  const providerFeeUsd = calculateProviderFee(quote).minus(appFeeUsd);
206
216
  const provider = subsidizedFeeUsd.gt(0)
207
217
  ? { usd: '0', fiat: '0' }
208
- : getFiatValueFromUsd(providerFeeUsd, usdToFiatRate);
218
+ : (0, amounts_1.getFiatValueFromUsd)(providerFeeUsd, usdToFiatRate);
209
219
  const { gasLimits, isGasFeeToken: isSourceGasFeeToken, ...sourceNetwork } = await calculateSourceNetworkCost(quote, messenger, request, fullRequest.transaction);
210
220
  const targetNetwork = {
211
221
  usd: '0',
@@ -214,7 +224,7 @@ async function normalizeQuote(quote, request, fullRequest) {
214
224
  const sourceAmount = {
215
225
  human: currencyIn.amountFormatted,
216
226
  raw: currencyIn.amount,
217
- ...getFiatValueFromUsd(new bignumber_js_1.BigNumber(currencyIn.amountUsd), usdToFiatRate),
227
+ ...(0, amounts_1.getFiatValueFromUsd)(new bignumber_js_1.BigNumber(currencyIn.amountUsd), usdToFiatRate),
218
228
  };
219
229
  const isTargetStablecoin = isStablecoin(request.targetChainId, request.targetTokenAddress);
220
230
  const additionalTargetAmountUsd = quote.request.tradeType === 'EXACT_INPUT'
@@ -227,7 +237,7 @@ async function normalizeQuote(quote, request, fullRequest) {
227
237
  ? new bignumber_js_1.BigNumber(currencyOut.amountFormatted)
228
238
  : new bignumber_js_1.BigNumber(currencyOut.amountUsd);
229
239
  const targetAmountUsd = baseTargetAmountUsd.plus(additionalTargetAmountUsd);
230
- const targetAmount = getFiatValueFromUsd(targetAmountUsd, usdToFiatRate);
240
+ const targetAmount = (0, amounts_1.getFiatValueFromUsd)(targetAmountUsd, usdToFiatRate);
231
241
  const metamask = {
232
242
  gasLimits,
233
243
  };
@@ -266,20 +276,6 @@ function calculateDustUsd(quote, request) {
266
276
  const dustRaw = new bignumber_js_1.BigNumber(minimumAmount).minus(request.targetAmountMinimum);
267
277
  return dustRaw.shiftedBy(-targetDecimals).multipliedBy(targetUsdRate);
268
278
  }
269
- /**
270
- * Converts USD value to fiat value.
271
- *
272
- * @param usdValue - USD value.
273
- * @param usdToFiatRate - USD to fiat rate.
274
- * @returns Fiat value.
275
- */
276
- function getFiatValueFromUsd(usdValue, usdToFiatRate) {
277
- const fiatValue = usdValue.multipliedBy(usdToFiatRate);
278
- return {
279
- usd: usdValue.toString(10),
280
- fiat: fiatValue.toString(10),
281
- };
282
- }
283
279
  /**
284
280
  * Calculates USD to fiat rate.
285
281
  *
@@ -320,7 +316,6 @@ async function calculateSourceNetworkCost(quote, messenger, request, transaction
320
316
  const relayParams = quote.steps
321
317
  .flatMap((step) => step.items)
322
318
  .map((item) => item.data);
323
- const { relayDisabledGasStationChains } = (0, feature_flags_1.getFeatureFlags)(messenger);
324
319
  const { chainId, data, maxFeePerGas, maxPriorityFeePerGas, to, value } = relayParams[0];
325
320
  const { totalGasEstimate, totalGasLimit, gasLimits } = await calculateSourceNetworkGasLimit(relayParams, messenger, request.isPostQuote ? transaction : undefined);
326
321
  log('Gas limit', {
@@ -348,16 +343,14 @@ async function calculateSourceNetworkCost(quote, messenger, request, transaction
348
343
  if (new bignumber_js_1.BigNumber(nativeBalance).isGreaterThanOrEqualTo(max.raw)) {
349
344
  return result;
350
345
  }
351
- if (relayDisabledGasStationChains.includes(sourceChainId)) {
346
+ const gasStationEligibility = (0, gas_station_1.getGasStationEligibility)(messenger, sourceChainId);
347
+ if (gasStationEligibility.isDisabledChain) {
352
348
  log('Skipping gas station as disabled chain', {
353
349
  sourceChainId,
354
- disabledChainIds: relayDisabledGasStationChains,
355
350
  });
356
351
  return result;
357
352
  }
358
- const supportedChains = (0, feature_flags_1.getEIP7702SupportedChains)(messenger);
359
- const chainSupportsGasStation = supportedChains.some((supportedChainId) => supportedChainId.toLowerCase() === sourceChainId.toLowerCase());
360
- if (!chainSupportsGasStation) {
353
+ if (!gasStationEligibility.chainSupportsGasStation) {
361
354
  log('Skipping gas station as chain does not support EIP-7702', {
362
355
  sourceChainId,
363
356
  });
@@ -367,40 +360,20 @@ async function calculateSourceNetworkCost(quote, messenger, request, transaction
367
360
  nativeBalance,
368
361
  max: max.raw,
369
362
  });
370
- const gasFeeTokens = await messenger.call('TransactionController:getGasFeeTokens', {
371
- chainId: sourceChainId,
372
- data,
373
- from,
374
- to,
375
- value: (0, controller_utils_1.toHex)(value ?? '0'),
376
- });
377
- log('Source gas fee tokens', { gasFeeTokens });
378
- const gasFeeToken = gasFeeTokens.find((singleGasFeeToken) => singleGasFeeToken.tokenAddress.toLowerCase() ===
379
- sourceTokenAddress.toLowerCase());
380
- if (!gasFeeToken) {
381
- log('No matching gas fee token found', {
382
- sourceTokenAddress,
383
- gasFeeTokens,
384
- });
385
- return result;
386
- }
387
- let finalAmount = gasFeeToken.amount;
388
- const hasMultipleTransactions = relayParams.length > 1 || gasLimits.length > 1;
389
- if (hasMultipleTransactions) {
390
- const gasRate = new bignumber_js_1.BigNumber(gasFeeToken.amount, 16).dividedBy(gasFeeToken.gas, 16);
391
- const finalAmountValue = gasRate.multipliedBy(totalGasEstimate);
392
- finalAmount = (0, controller_utils_1.toHex)(finalAmountValue.toFixed(0));
393
- log('Estimated gas fee token amount for batch', {
394
- finalAmount: finalAmountValue.toString(10),
395
- gasRate: gasRate.toString(10),
396
- totalGasEstimate,
397
- });
398
- }
399
- const finalGasFeeToken = { ...gasFeeToken, amount: finalAmount };
400
- const gasFeeTokenCost = (0, gas_1.calculateGasFeeTokenCost)({
401
- chainId: sourceChainId,
402
- gasFeeToken: finalGasFeeToken,
363
+ const gasFeeTokenCost = await (0, gas_station_1.getGasStationCostInSourceTokenRaw)({
364
+ firstStepData: {
365
+ data,
366
+ to,
367
+ value,
368
+ },
403
369
  messenger,
370
+ request: {
371
+ from,
372
+ sourceChainId,
373
+ sourceTokenAddress,
374
+ },
375
+ totalGasEstimate,
376
+ totalItemCount: Math.max(relayParams.length, gasLimits.length),
404
377
  });
405
378
  if (!gasFeeTokenCost) {
406
379
  return result;