@metamask/transaction-pay-controller 16.3.0 → 16.4.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.
- package/CHANGELOG.md +10 -1
- package/dist/TransactionPayController.cjs +9 -0
- package/dist/TransactionPayController.cjs.map +1 -1
- package/dist/TransactionPayController.d.cts +2 -1
- package/dist/TransactionPayController.d.cts.map +1 -1
- package/dist/TransactionPayController.d.mts +2 -1
- package/dist/TransactionPayController.d.mts.map +1 -1
- package/dist/TransactionPayController.mjs +9 -0
- package/dist/TransactionPayController.mjs.map +1 -1
- package/dist/actions/update-fiat-payment.cjs +29 -0
- package/dist/actions/update-fiat-payment.cjs.map +1 -0
- package/dist/actions/update-fiat-payment.d.cts +14 -0
- package/dist/actions/update-fiat-payment.d.cts.map +1 -0
- package/dist/actions/update-fiat-payment.d.mts +14 -0
- package/dist/actions/update-fiat-payment.d.mts.map +1 -0
- package/dist/actions/update-fiat-payment.mjs +25 -0
- package/dist/actions/update-fiat-payment.mjs.map +1 -0
- package/dist/actions/update-payment-token.cjs +1 -0
- package/dist/actions/update-payment-token.cjs.map +1 -1
- package/dist/actions/update-payment-token.d.cts.map +1 -1
- package/dist/actions/update-payment-token.d.mts.map +1 -1
- package/dist/actions/update-payment-token.mjs +1 -0
- package/dist/actions/update-payment-token.mjs.map +1 -1
- package/dist/constants.cjs +1 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +1 -0
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.mts +1 -0
- package/dist/constants.d.mts.map +1 -1
- package/dist/constants.mjs +1 -0
- package/dist/constants.mjs.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/strategy/across/AcrossStrategy.cjs +28 -0
- package/dist/strategy/across/AcrossStrategy.cjs.map +1 -0
- package/dist/strategy/across/AcrossStrategy.d.cts +8 -0
- package/dist/strategy/across/AcrossStrategy.d.cts.map +1 -0
- package/dist/strategy/across/AcrossStrategy.d.mts +8 -0
- package/dist/strategy/across/AcrossStrategy.d.mts.map +1 -0
- package/dist/strategy/across/AcrossStrategy.mjs +24 -0
- package/dist/strategy/across/AcrossStrategy.mjs.map +1 -0
- package/dist/strategy/across/across-quotes.cjs +356 -0
- package/dist/strategy/across/across-quotes.cjs.map +1 -0
- package/dist/strategy/across/across-quotes.d.cts +10 -0
- package/dist/strategy/across/across-quotes.d.cts.map +1 -0
- package/dist/strategy/across/across-quotes.d.mts +10 -0
- package/dist/strategy/across/across-quotes.d.mts.map +1 -0
- package/dist/strategy/across/across-quotes.mjs +352 -0
- package/dist/strategy/across/across-quotes.mjs.map +1 -0
- package/dist/strategy/across/across-submit.cjs +241 -0
- package/dist/strategy/across/across-submit.cjs.map +1 -0
- package/dist/strategy/across/across-submit.d.cts +13 -0
- package/dist/strategy/across/across-submit.d.cts.map +1 -0
- package/dist/strategy/across/across-submit.d.mts +13 -0
- package/dist/strategy/across/across-submit.d.mts.map +1 -0
- package/dist/strategy/across/across-submit.mjs +237 -0
- package/dist/strategy/across/across-submit.mjs.map +1 -0
- package/dist/strategy/across/types.cjs +3 -0
- package/dist/strategy/across/types.cjs.map +1 -0
- package/dist/strategy/across/types.d.cts +89 -0
- package/dist/strategy/across/types.d.cts.map +1 -0
- package/dist/strategy/across/types.d.mts +89 -0
- package/dist/strategy/across/types.d.mts.map +1 -0
- package/dist/strategy/across/types.mjs +2 -0
- package/dist/strategy/across/types.mjs.map +1 -0
- package/dist/strategy/relay/RelayStrategy.cjs +5 -0
- package/dist/strategy/relay/RelayStrategy.cjs.map +1 -1
- package/dist/strategy/relay/RelayStrategy.d.cts +1 -0
- package/dist/strategy/relay/RelayStrategy.d.cts.map +1 -1
- package/dist/strategy/relay/RelayStrategy.d.mts +1 -0
- package/dist/strategy/relay/RelayStrategy.d.mts.map +1 -1
- package/dist/strategy/relay/RelayStrategy.mjs +5 -0
- package/dist/strategy/relay/RelayStrategy.mjs.map +1 -1
- package/dist/strategy/relay/gas-station.cjs +92 -0
- package/dist/strategy/relay/gas-station.cjs.map +1 -0
- package/dist/strategy/relay/gas-station.d.cts +22 -0
- package/dist/strategy/relay/gas-station.d.cts.map +1 -0
- package/dist/strategy/relay/gas-station.d.mts +22 -0
- package/dist/strategy/relay/gas-station.d.mts.map +1 -0
- package/dist/strategy/relay/gas-station.mjs +87 -0
- package/dist/strategy/relay/gas-station.mjs.map +1 -0
- package/dist/strategy/relay/relay-max-gas-station.cjs +242 -0
- package/dist/strategy/relay/relay-max-gas-station.cjs.map +1 -0
- package/dist/strategy/relay/relay-max-gas-station.d.cts +22 -0
- package/dist/strategy/relay/relay-max-gas-station.d.cts.map +1 -0
- package/dist/strategy/relay/relay-max-gas-station.d.mts +22 -0
- package/dist/strategy/relay/relay-max-gas-station.d.mts.map +1 -0
- package/dist/strategy/relay/relay-max-gas-station.mjs +238 -0
- package/dist/strategy/relay/relay-max-gas-station.mjs.map +1 -0
- package/dist/strategy/relay/relay-quotes.cjs +32 -59
- package/dist/strategy/relay/relay-quotes.cjs.map +1 -1
- package/dist/strategy/relay/relay-quotes.d.cts.map +1 -1
- package/dist/strategy/relay/relay-quotes.d.mts.map +1 -1
- package/dist/strategy/relay/relay-quotes.mjs +29 -56
- package/dist/strategy/relay/relay-quotes.mjs.map +1 -1
- package/dist/strategy/relay/types.cjs.map +1 -1
- package/dist/strategy/relay/types.d.cts +1 -0
- package/dist/strategy/relay/types.d.cts.map +1 -1
- package/dist/strategy/relay/types.d.mts +1 -0
- package/dist/strategy/relay/types.d.mts.map +1 -1
- package/dist/strategy/relay/types.mjs.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +24 -1
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +24 -1
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs.map +1 -1
- package/dist/utils/amounts.cjs +40 -0
- package/dist/utils/amounts.cjs.map +1 -0
- package/dist/utils/amounts.d.cts +18 -0
- package/dist/utils/amounts.d.cts.map +1 -0
- package/dist/utils/amounts.d.mts +18 -0
- package/dist/utils/amounts.d.mts.map +1 -0
- package/dist/utils/amounts.mjs +35 -0
- package/dist/utils/amounts.mjs.map +1 -0
- package/dist/utils/feature-flags.cjs +45 -6
- package/dist/utils/feature-flags.cjs.map +1 -1
- package/dist/utils/feature-flags.d.cts +45 -2
- package/dist/utils/feature-flags.d.cts.map +1 -1
- package/dist/utils/feature-flags.d.mts +45 -2
- package/dist/utils/feature-flags.d.mts.map +1 -1
- package/dist/utils/feature-flags.mjs +42 -5
- package/dist/utils/feature-flags.mjs.map +1 -1
- package/dist/utils/gas.cjs +46 -1
- package/dist/utils/gas.cjs.map +1 -1
- package/dist/utils/gas.d.cts +18 -0
- package/dist/utils/gas.d.cts.map +1 -1
- package/dist/utils/gas.d.mts +18 -0
- package/dist/utils/gas.d.mts.map +1 -1
- package/dist/utils/gas.mjs +44 -0
- package/dist/utils/gas.mjs.map +1 -1
- package/dist/utils/strategy.cjs +3 -0
- package/dist/utils/strategy.cjs.map +1 -1
- package/dist/utils/strategy.d.cts.map +1 -1
- package/dist/utils/strategy.d.mts.map +1 -1
- package/dist/utils/strategy.mjs +3 -0
- package/dist/utils/strategy.mjs.map +1 -1
- package/dist/utils/totals.cjs +4 -19
- package/dist/utils/totals.cjs.map +1 -1
- package/dist/utils/totals.d.cts.map +1 -1
- package/dist/utils/totals.d.mts.map +1 -1
- package/dist/utils/totals.mjs +1 -16
- package/dist/utils/totals.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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;
|