@metamask/transaction-pay-controller 16.2.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 +22 -1
- package/dist/TransactionPayController.cjs +11 -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 +11 -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 +38 -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 +35 -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 +44 -1
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +44 -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/quotes.cjs +8 -3
- package/dist/utils/quotes.cjs.map +1 -1
- package/dist/utils/quotes.d.cts.map +1 -1
- package/dist/utils/quotes.d.mts.map +1 -1
- package/dist/utils/quotes.mjs +8 -3
- package/dist/utils/quotes.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 +3 -3
|
@@ -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
|
*
|
|
@@ -73,6 +83,12 @@ async function getSingleQuote(request, fullRequest) {
|
|
|
73
83
|
if (!request.isPostQuote) {
|
|
74
84
|
await processTransactions(transaction, request, body, messenger);
|
|
75
85
|
}
|
|
86
|
+
else if (request.refundTo) {
|
|
87
|
+
// For post-quote flows, honour the caller-specified refund address so that
|
|
88
|
+
// failed Relay transactions refund to the correct account (e.g. the Predict
|
|
89
|
+
// Safe proxy) rather than defaulting to the EOA.
|
|
90
|
+
body.refundTo = request.refundTo;
|
|
91
|
+
}
|
|
76
92
|
const url = (0, feature_flags_1.getFeatureFlags)(messenger).relayQuoteUrl;
|
|
77
93
|
log('Request body', { body, url });
|
|
78
94
|
const response = await (0, controller_utils_1.successfulFetch)(url, {
|
|
@@ -191,15 +207,15 @@ async function normalizeQuote(quote, request, fullRequest) {
|
|
|
191
207
|
const { details } = quote;
|
|
192
208
|
const { currencyIn, currencyOut } = details;
|
|
193
209
|
const { usdToFiatRate } = getFiatRates(messenger, request);
|
|
194
|
-
const dust = getFiatValueFromUsd(calculateDustUsd(quote, request), usdToFiatRate);
|
|
210
|
+
const dust = (0, amounts_1.getFiatValueFromUsd)(calculateDustUsd(quote, request), usdToFiatRate);
|
|
195
211
|
const subsidizedFeeUsd = getSubsidizedFeeAmountUsd(quote);
|
|
196
212
|
const appFeeUsd = new bignumber_js_1.BigNumber(quote.fees?.app?.amountUsd ?? '0');
|
|
197
|
-
const metaMaskFee = getFiatValueFromUsd(appFeeUsd, usdToFiatRate);
|
|
213
|
+
const metaMaskFee = (0, amounts_1.getFiatValueFromUsd)(appFeeUsd, usdToFiatRate);
|
|
198
214
|
// Subtract app fee from provider fee since totalImpact.usd already includes it
|
|
199
215
|
const providerFeeUsd = calculateProviderFee(quote).minus(appFeeUsd);
|
|
200
216
|
const provider = subsidizedFeeUsd.gt(0)
|
|
201
217
|
? { usd: '0', fiat: '0' }
|
|
202
|
-
: getFiatValueFromUsd(providerFeeUsd, usdToFiatRate);
|
|
218
|
+
: (0, amounts_1.getFiatValueFromUsd)(providerFeeUsd, usdToFiatRate);
|
|
203
219
|
const { gasLimits, isGasFeeToken: isSourceGasFeeToken, ...sourceNetwork } = await calculateSourceNetworkCost(quote, messenger, request, fullRequest.transaction);
|
|
204
220
|
const targetNetwork = {
|
|
205
221
|
usd: '0',
|
|
@@ -208,7 +224,7 @@ async function normalizeQuote(quote, request, fullRequest) {
|
|
|
208
224
|
const sourceAmount = {
|
|
209
225
|
human: currencyIn.amountFormatted,
|
|
210
226
|
raw: currencyIn.amount,
|
|
211
|
-
...getFiatValueFromUsd(new bignumber_js_1.BigNumber(currencyIn.amountUsd), usdToFiatRate),
|
|
227
|
+
...(0, amounts_1.getFiatValueFromUsd)(new bignumber_js_1.BigNumber(currencyIn.amountUsd), usdToFiatRate),
|
|
212
228
|
};
|
|
213
229
|
const isTargetStablecoin = isStablecoin(request.targetChainId, request.targetTokenAddress);
|
|
214
230
|
const additionalTargetAmountUsd = quote.request.tradeType === 'EXACT_INPUT'
|
|
@@ -221,7 +237,7 @@ async function normalizeQuote(quote, request, fullRequest) {
|
|
|
221
237
|
? new bignumber_js_1.BigNumber(currencyOut.amountFormatted)
|
|
222
238
|
: new bignumber_js_1.BigNumber(currencyOut.amountUsd);
|
|
223
239
|
const targetAmountUsd = baseTargetAmountUsd.plus(additionalTargetAmountUsd);
|
|
224
|
-
const targetAmount = getFiatValueFromUsd(targetAmountUsd, usdToFiatRate);
|
|
240
|
+
const targetAmount = (0, amounts_1.getFiatValueFromUsd)(targetAmountUsd, usdToFiatRate);
|
|
225
241
|
const metamask = {
|
|
226
242
|
gasLimits,
|
|
227
243
|
};
|
|
@@ -260,20 +276,6 @@ function calculateDustUsd(quote, request) {
|
|
|
260
276
|
const dustRaw = new bignumber_js_1.BigNumber(minimumAmount).minus(request.targetAmountMinimum);
|
|
261
277
|
return dustRaw.shiftedBy(-targetDecimals).multipliedBy(targetUsdRate);
|
|
262
278
|
}
|
|
263
|
-
/**
|
|
264
|
-
* Converts USD value to fiat value.
|
|
265
|
-
*
|
|
266
|
-
* @param usdValue - USD value.
|
|
267
|
-
* @param usdToFiatRate - USD to fiat rate.
|
|
268
|
-
* @returns Fiat value.
|
|
269
|
-
*/
|
|
270
|
-
function getFiatValueFromUsd(usdValue, usdToFiatRate) {
|
|
271
|
-
const fiatValue = usdValue.multipliedBy(usdToFiatRate);
|
|
272
|
-
return {
|
|
273
|
-
usd: usdValue.toString(10),
|
|
274
|
-
fiat: fiatValue.toString(10),
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
279
|
/**
|
|
278
280
|
* Calculates USD to fiat rate.
|
|
279
281
|
*
|
|
@@ -314,7 +316,6 @@ async function calculateSourceNetworkCost(quote, messenger, request, transaction
|
|
|
314
316
|
const relayParams = quote.steps
|
|
315
317
|
.flatMap((step) => step.items)
|
|
316
318
|
.map((item) => item.data);
|
|
317
|
-
const { relayDisabledGasStationChains } = (0, feature_flags_1.getFeatureFlags)(messenger);
|
|
318
319
|
const { chainId, data, maxFeePerGas, maxPriorityFeePerGas, to, value } = relayParams[0];
|
|
319
320
|
const { totalGasEstimate, totalGasLimit, gasLimits } = await calculateSourceNetworkGasLimit(relayParams, messenger, request.isPostQuote ? transaction : undefined);
|
|
320
321
|
log('Gas limit', {
|
|
@@ -342,16 +343,14 @@ async function calculateSourceNetworkCost(quote, messenger, request, transaction
|
|
|
342
343
|
if (new bignumber_js_1.BigNumber(nativeBalance).isGreaterThanOrEqualTo(max.raw)) {
|
|
343
344
|
return result;
|
|
344
345
|
}
|
|
345
|
-
|
|
346
|
+
const gasStationEligibility = (0, gas_station_1.getGasStationEligibility)(messenger, sourceChainId);
|
|
347
|
+
if (gasStationEligibility.isDisabledChain) {
|
|
346
348
|
log('Skipping gas station as disabled chain', {
|
|
347
349
|
sourceChainId,
|
|
348
|
-
disabledChainIds: relayDisabledGasStationChains,
|
|
349
350
|
});
|
|
350
351
|
return result;
|
|
351
352
|
}
|
|
352
|
-
|
|
353
|
-
const chainSupportsGasStation = supportedChains.some((supportedChainId) => supportedChainId.toLowerCase() === sourceChainId.toLowerCase());
|
|
354
|
-
if (!chainSupportsGasStation) {
|
|
353
|
+
if (!gasStationEligibility.chainSupportsGasStation) {
|
|
355
354
|
log('Skipping gas station as chain does not support EIP-7702', {
|
|
356
355
|
sourceChainId,
|
|
357
356
|
});
|
|
@@ -361,40 +360,20 @@ async function calculateSourceNetworkCost(quote, messenger, request, transaction
|
|
|
361
360
|
nativeBalance,
|
|
362
361
|
max: max.raw,
|
|
363
362
|
});
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
});
|
|
371
|
-
log('Source gas fee tokens', { gasFeeTokens });
|
|
372
|
-
const gasFeeToken = gasFeeTokens.find((singleGasFeeToken) => singleGasFeeToken.tokenAddress.toLowerCase() ===
|
|
373
|
-
sourceTokenAddress.toLowerCase());
|
|
374
|
-
if (!gasFeeToken) {
|
|
375
|
-
log('No matching gas fee token found', {
|
|
376
|
-
sourceTokenAddress,
|
|
377
|
-
gasFeeTokens,
|
|
378
|
-
});
|
|
379
|
-
return result;
|
|
380
|
-
}
|
|
381
|
-
let finalAmount = gasFeeToken.amount;
|
|
382
|
-
const hasMultipleTransactions = relayParams.length > 1 || gasLimits.length > 1;
|
|
383
|
-
if (hasMultipleTransactions) {
|
|
384
|
-
const gasRate = new bignumber_js_1.BigNumber(gasFeeToken.amount, 16).dividedBy(gasFeeToken.gas, 16);
|
|
385
|
-
const finalAmountValue = gasRate.multipliedBy(totalGasEstimate);
|
|
386
|
-
finalAmount = (0, controller_utils_1.toHex)(finalAmountValue.toFixed(0));
|
|
387
|
-
log('Estimated gas fee token amount for batch', {
|
|
388
|
-
finalAmount: finalAmountValue.toString(10),
|
|
389
|
-
gasRate: gasRate.toString(10),
|
|
390
|
-
totalGasEstimate,
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
const finalGasFeeToken = { ...gasFeeToken, amount: finalAmount };
|
|
394
|
-
const gasFeeTokenCost = (0, gas_1.calculateGasFeeTokenCost)({
|
|
395
|
-
chainId: sourceChainId,
|
|
396
|
-
gasFeeToken: finalGasFeeToken,
|
|
363
|
+
const gasFeeTokenCost = await (0, gas_station_1.getGasStationCostInSourceTokenRaw)({
|
|
364
|
+
firstStepData: {
|
|
365
|
+
data,
|
|
366
|
+
to,
|
|
367
|
+
value,
|
|
368
|
+
},
|
|
397
369
|
messenger,
|
|
370
|
+
request: {
|
|
371
|
+
from,
|
|
372
|
+
sourceChainId,
|
|
373
|
+
sourceTokenAddress,
|
|
374
|
+
},
|
|
375
|
+
totalGasEstimate,
|
|
376
|
+
totalItemCount: Math.max(relayParams.length, gasLimits.length),
|
|
398
377
|
});
|
|
399
378
|
if (!gasFeeTokenCost) {
|
|
400
379
|
return result;
|