mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Only check if a user has enough token balance before calling STX (#15218)
This commit is contained in:
parent
753666d9c2
commit
d255fcdefb
@ -1891,6 +1891,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
fetchSmartTransactionFees: smartTransactionsController.getFees.bind(
|
||||
smartTransactionsController,
|
||||
),
|
||||
clearSmartTransactionFees: smartTransactionsController.clearFees.bind(
|
||||
smartTransactionsController,
|
||||
),
|
||||
submitSignedTransactions:
|
||||
smartTransactionsController.submitSignedTransactions.bind(
|
||||
smartTransactionsController,
|
||||
|
@ -132,7 +132,7 @@
|
||||
"@metamask/providers": "^9.0.0",
|
||||
"@metamask/rpc-methods": "^0.18.1",
|
||||
"@metamask/slip44": "^2.1.0",
|
||||
"@metamask/smart-transactions-controller": "^2.1.0",
|
||||
"@metamask/smart-transactions-controller": "^2.3.0",
|
||||
"@metamask/snap-controllers": "^0.18.1",
|
||||
"@ngraveio/bc-ur": "^1.1.6",
|
||||
"@popperjs/core": "^2.4.0",
|
||||
|
@ -928,10 +928,11 @@ export const signAndSendSwapsSmartTransaction = ({
|
||||
};
|
||||
}
|
||||
const fees = await dispatch(
|
||||
fetchSwapsSmartTransactionFees(
|
||||
fetchSwapsSmartTransactionFees({
|
||||
unsignedTransaction,
|
||||
updatedApproveTxParams,
|
||||
),
|
||||
approveTxParams: updatedApproveTxParams,
|
||||
fallbackOnNotEnoughFunds: true,
|
||||
}),
|
||||
);
|
||||
if (!fees) {
|
||||
log.error('"fetchSwapsSmartTransactionFees" failed');
|
||||
@ -994,7 +995,7 @@ export const signAndSendSwapsSmartTransaction = ({
|
||||
} = getState();
|
||||
if (e.message.startsWith('Fetch error:') && isFeatureFlagLoaded) {
|
||||
const errorObj = parseSmartTransactionsError(e.message);
|
||||
dispatch(setCurrentSmartTransactionsError(errorObj?.type));
|
||||
dispatch(setCurrentSmartTransactionsError(errorObj?.error));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1306,10 +1307,11 @@ export function fetchMetaSwapsGasPriceEstimates() {
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchSwapsSmartTransactionFees(
|
||||
export function fetchSwapsSmartTransactionFees({
|
||||
unsignedTransaction,
|
||||
approveTxParams,
|
||||
) {
|
||||
fallbackOnNotEnoughFunds = false,
|
||||
}) {
|
||||
return async (dispatch, getState) => {
|
||||
const {
|
||||
swaps: { isFeatureFlagLoaded },
|
||||
@ -1321,7 +1323,12 @@ export function fetchSwapsSmartTransactionFees(
|
||||
} catch (e) {
|
||||
if (e.message.startsWith('Fetch error:') && isFeatureFlagLoaded) {
|
||||
const errorObj = parseSmartTransactionsError(e.message);
|
||||
dispatch(setCurrentSmartTransactionsError(errorObj?.type));
|
||||
if (
|
||||
fallbackOnNotEnoughFunds ||
|
||||
errorObj?.error !== stxErrorTypes.NOT_ENOUGH_FUNDS
|
||||
) {
|
||||
dispatch(setCurrentSmartTransactionsError(errorObj?.error));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -1338,7 +1345,7 @@ export function cancelSwapsSmartTransaction(uuid) {
|
||||
} = getState();
|
||||
if (e.message.startsWith('Fetch error:') && isFeatureFlagLoaded) {
|
||||
const errorObj = parseSmartTransactionsError(e.message);
|
||||
dispatch(setCurrentSmartTransactionsError(errorObj?.type));
|
||||
dispatch(setCurrentSmartTransactionsError(errorObj?.error));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -162,6 +162,15 @@ export function hexWEIToDecETH(hexWEI) {
|
||||
});
|
||||
}
|
||||
|
||||
export function decWEIToDecETH(hexWEI) {
|
||||
return conversionUtil(hexWEI, {
|
||||
fromNumericBase: 'dec',
|
||||
toNumericBase: 'dec',
|
||||
fromDenomination: 'WEI',
|
||||
toDenomination: 'ETH',
|
||||
});
|
||||
}
|
||||
|
||||
export function addHexes(aHexWEI, bHexWEI) {
|
||||
return addCurrencies(aHexWEI, bHexWEI, {
|
||||
aBase: 16,
|
||||
|
@ -39,4 +39,16 @@ describe('conversion utils', () => {
|
||||
expect(weiValue).toStrictEqual('1000000000000000000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decWEIToDecETH', () => {
|
||||
it('converts 10000000000000 WEI to ETH', () => {
|
||||
const ethDec = utils.decWEIToDecETH('10000000000000');
|
||||
expect('0.00001').toStrictEqual(ethDec);
|
||||
});
|
||||
|
||||
it('converts 9358749494527040 WEI to ETH', () => {
|
||||
const ethDec = utils.decWEIToDecETH('9358749494527040');
|
||||
expect('0.009358749').toStrictEqual(ethDec);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -58,6 +58,7 @@ import {
|
||||
getMaxSlippage,
|
||||
getIsFeatureFlagLoaded,
|
||||
getCurrentSmartTransactionsError,
|
||||
getSmartTransactionFees,
|
||||
} from '../../../ducks/swaps/swaps';
|
||||
import {
|
||||
getSwapsDefaultToken,
|
||||
@ -100,6 +101,7 @@ import {
|
||||
clearSwapsQuotes,
|
||||
stopPollingForQuotes,
|
||||
setSmartTransactionsOptInStatus,
|
||||
clearSmartTransactionFees,
|
||||
} from '../../../store/actions';
|
||||
import {
|
||||
countDecimals,
|
||||
@ -165,6 +167,7 @@ export default function BuildQuote({
|
||||
const currentSmartTransactionsEnabled = useSelector(
|
||||
getCurrentSmartTransactionsEnabled,
|
||||
);
|
||||
const smartTransactionFees = useSelector(getSmartTransactionFees);
|
||||
const smartTransactionsOptInPopoverDisplayed =
|
||||
smartTransactionsOptInStatus !== undefined;
|
||||
const currentSmartTransactionsError = useSelector(
|
||||
@ -470,6 +473,13 @@ export default function BuildQuote({
|
||||
trackBuildQuotePageLoadedEvent();
|
||||
}, [dispatch, trackBuildQuotePageLoadedEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (smartTransactionsEnabled && smartTransactionFees?.tradeTxFees) {
|
||||
// We want to clear STX fees, because we only want to use fresh ones on the View Quote page.
|
||||
clearSmartTransactionFees();
|
||||
}
|
||||
}, [smartTransactionsEnabled, smartTransactionFees]);
|
||||
|
||||
const BlockExplorerLink = () => {
|
||||
return (
|
||||
<a
|
||||
|
@ -26,6 +26,7 @@ setBackgroundConnection({
|
||||
setBackgroundSwapRouteState: jest.fn(),
|
||||
clearSwapsQuotes: jest.fn(),
|
||||
stopPollingForQuotes: jest.fn(),
|
||||
clearSmartTransactionFees: jest.fn(),
|
||||
});
|
||||
|
||||
describe('BuildQuote', () => {
|
||||
|
@ -45,6 +45,7 @@ import {
|
||||
signAndSendSwapsSmartTransaction,
|
||||
getSwapsNetworkConfig,
|
||||
getSmartTransactionsEnabled,
|
||||
getSmartTransactionsError,
|
||||
getCurrentSmartTransactionsError,
|
||||
getCurrentSmartTransactionsErrorMessageDismissed,
|
||||
getSwapsSTXLoading,
|
||||
@ -74,6 +75,7 @@ import {
|
||||
showModal,
|
||||
setSwapsQuotesPollingLimitEnabled,
|
||||
} from '../../../store/actions';
|
||||
import { SET_SMART_TRANSACTIONS_ERROR } from '../../../store/actionConstants';
|
||||
import {
|
||||
ASSET_ROUTE,
|
||||
BUILD_QUOTE_ROUTE,
|
||||
@ -91,6 +93,7 @@ import {
|
||||
decGWEIToHexWEI,
|
||||
hexWEIToDecGWEI,
|
||||
addHexes,
|
||||
decWEIToDecETH,
|
||||
} from '../../../helpers/utils/conversions.util';
|
||||
import MainQuoteSummary from '../main-quote-summary';
|
||||
import { calcGasTotal } from '../../send/send.utils';
|
||||
@ -184,6 +187,7 @@ export default function ViewQuote() {
|
||||
const currentSmartTransactionsError = useSelector(
|
||||
getCurrentSmartTransactionsError,
|
||||
);
|
||||
const smartTransactionsError = useSelector(getSmartTransactionsError);
|
||||
const currentSmartTransactionsErrorMessageDismissed = useSelector(
|
||||
getCurrentSmartTransactionsErrorMessageDismissed,
|
||||
);
|
||||
@ -443,15 +447,35 @@ export default function ViewQuote() {
|
||||
)
|
||||
: null;
|
||||
|
||||
const destinationToken = useSelector(getDestinationTokenInfo, isEqual);
|
||||
let ethBalanceNeededStx;
|
||||
if (smartTransactionsError?.balanceNeededWei) {
|
||||
ethBalanceNeededStx = decWEIToDecETH(
|
||||
smartTransactionsError.balanceNeededWei -
|
||||
smartTransactionsError.currentBalanceWei,
|
||||
);
|
||||
}
|
||||
|
||||
const destinationToken = useSelector(getDestinationTokenInfo, isEqual);
|
||||
useEffect(() => {
|
||||
if (insufficientTokens || insufficientEth) {
|
||||
if (currentSmartTransactionsEnabled && smartTransactionsOptInStatus) {
|
||||
if (insufficientTokens) {
|
||||
dispatch(setBalanceError(true));
|
||||
} else if (balanceError && !insufficientTokens) {
|
||||
dispatch(setBalanceError(false));
|
||||
}
|
||||
} else if (insufficientTokens || insufficientEth) {
|
||||
dispatch(setBalanceError(true));
|
||||
} else if (balanceError && !insufficientTokens && !insufficientEth) {
|
||||
dispatch(setBalanceError(false));
|
||||
}
|
||||
}, [insufficientTokens, insufficientEth, balanceError, dispatch]);
|
||||
}, [
|
||||
insufficientTokens,
|
||||
insufficientEth,
|
||||
balanceError,
|
||||
dispatch,
|
||||
currentSmartTransactionsEnabled,
|
||||
smartTransactionsOptInStatus,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentTime = Date.now();
|
||||
@ -480,8 +504,24 @@ export default function ViewQuote() {
|
||||
}
|
||||
}, [originalApproveAmount, approveAmount]);
|
||||
|
||||
// If it's not a Smart Transaction and ETH balance is needed, we want to show a warning.
|
||||
const isNotStxAndEthBalanceIsNeeded =
|
||||
(!currentSmartTransactionsEnabled || !smartTransactionsOptInStatus) &&
|
||||
ethBalanceNeeded;
|
||||
|
||||
// If it's a Smart Transaction and ETH balance is needed, we want to show a warning.
|
||||
const isStxAndEthBalanceIsNeeded =
|
||||
currentSmartTransactionsEnabled &&
|
||||
smartTransactionsOptInStatus &&
|
||||
ethBalanceNeededStx;
|
||||
|
||||
// Indicates if we should show to a user a warning about insufficient funds for swapping.
|
||||
const showInsufficientWarning =
|
||||
(balanceError || tokenBalanceNeeded || ethBalanceNeeded) && !warningHidden;
|
||||
(balanceError ||
|
||||
tokenBalanceNeeded ||
|
||||
isNotStxAndEthBalanceIsNeeded ||
|
||||
isStxAndEthBalanceIsNeeded) &&
|
||||
!warningHidden;
|
||||
|
||||
const hardwareWalletUsed = useSelector(isHardwareWallet);
|
||||
const hardwareWalletType = useSelector(getHardwareWalletType);
|
||||
@ -656,12 +696,11 @@ export default function ViewQuote() {
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
const actionableBalanceErrorMessage = tokenBalanceUnavailable
|
||||
? t('swapTokenBalanceUnavailable', [sourceTokenSymbol])
|
||||
: t('swapApproveNeedMoreTokens', [
|
||||
<span key="swapApproveNeedMoreTokens-1" className="view-quote__bold">
|
||||
{tokenBalanceNeeded || ethBalanceNeeded}
|
||||
{tokenBalanceNeeded || ethBalanceNeededStx || ethBalanceNeeded}
|
||||
</span>,
|
||||
tokenBalanceNeeded && !(sourceTokenSymbol === defaultSwapsToken.symbol)
|
||||
? sourceTokenSymbol
|
||||
@ -755,14 +794,18 @@ export default function ViewQuote() {
|
||||
baseAndPriorityFeePerGas === undefined) ||
|
||||
(!networkAndAccountSupports1559 &&
|
||||
(gasPrice === null || gasPrice === undefined)) ||
|
||||
(currentSmartTransactionsEnabled && currentSmartTransactionsError),
|
||||
(currentSmartTransactionsEnabled &&
|
||||
(currentSmartTransactionsError || smartTransactionsError)) ||
|
||||
(currentSmartTransactionsEnabled &&
|
||||
smartTransactionsOptInStatus &&
|
||||
!smartTransactionFees?.tradeTxFees),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
currentSmartTransactionsEnabled &&
|
||||
smartTransactionsOptInStatus &&
|
||||
!isSwapButtonDisabled
|
||||
!insufficientTokens
|
||||
) {
|
||||
const unsignedTx = {
|
||||
from: unsignedTransaction.from,
|
||||
@ -774,10 +817,22 @@ export default function ViewQuote() {
|
||||
};
|
||||
intervalId = setInterval(() => {
|
||||
if (!swapsSTXLoading) {
|
||||
dispatch(fetchSwapsSmartTransactionFees(unsignedTx, approveTxParams));
|
||||
dispatch(
|
||||
fetchSwapsSmartTransactionFees({
|
||||
unsignedTransaction: unsignedTx,
|
||||
approveTxParams,
|
||||
fallbackOnNotEnoughFunds: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, swapsNetworkConfig.stxGetTransactionsRefreshTime);
|
||||
dispatch(fetchSwapsSmartTransactionFees(unsignedTx, approveTxParams));
|
||||
dispatch(
|
||||
fetchSwapsSmartTransactionFees({
|
||||
unsignedTransaction: unsignedTx,
|
||||
approveTxParams,
|
||||
fallbackOnNotEnoughFunds: false,
|
||||
}),
|
||||
);
|
||||
} else if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
@ -794,7 +849,7 @@ export default function ViewQuote() {
|
||||
unsignedTransaction.to,
|
||||
chainId,
|
||||
swapsNetworkConfig.stxGetTransactionsRefreshTime,
|
||||
isSwapButtonDisabled,
|
||||
insufficientTokens,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -820,6 +875,16 @@ export default function ViewQuote() {
|
||||
submitClicked,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentSmartTransactionsEnabled && smartTransactionsOptInStatus) {
|
||||
// Removes a smart transactions error when the component loads.
|
||||
dispatch({
|
||||
type: SET_SMART_TRANSACTIONS_ERROR,
|
||||
payload: null,
|
||||
});
|
||||
}
|
||||
}, [currentSmartTransactionsEnabled, smartTransactionsOptInStatus, dispatch]);
|
||||
|
||||
return (
|
||||
<div className="view-quote">
|
||||
<div
|
||||
@ -876,7 +941,8 @@ export default function ViewQuote() {
|
||||
/>
|
||||
{currentSmartTransactionsEnabled &&
|
||||
smartTransactionsOptInStatus &&
|
||||
!smartTransactionFees?.tradeTxFees && (
|
||||
!smartTransactionFees?.tradeTxFees &&
|
||||
!showInsufficientWarning && (
|
||||
<Box marginTop={0} marginBottom={10}>
|
||||
<PulseLoader />
|
||||
</Box>
|
||||
|
@ -3565,6 +3565,10 @@ export async function setSmartTransactionsOptInStatus(
|
||||
await promisifiedBackground.setSmartTransactionsOptInStatus(optInState);
|
||||
}
|
||||
|
||||
export function clearSmartTransactionFees() {
|
||||
promisifiedBackground.clearSmartTransactionFees();
|
||||
}
|
||||
|
||||
export function fetchSmartTransactionFees(
|
||||
unsignedTransaction,
|
||||
approveTxParams,
|
||||
@ -3574,17 +3578,23 @@ export function fetchSmartTransactionFees(
|
||||
approveTxParams.value = '0x0';
|
||||
}
|
||||
try {
|
||||
return await promisifiedBackground.fetchSmartTransactionFees(
|
||||
const smartTransactionFees =
|
||||
await promisifiedBackground.fetchSmartTransactionFees(
|
||||
unsignedTransaction,
|
||||
approveTxParams,
|
||||
);
|
||||
dispatch({
|
||||
type: actionConstants.SET_SMART_TRANSACTIONS_ERROR,
|
||||
payload: null,
|
||||
});
|
||||
return smartTransactionFees;
|
||||
} catch (e) {
|
||||
log.error(e);
|
||||
if (e.message.startsWith('Fetch error:')) {
|
||||
const errorObj = parseSmartTransactionsError(e.message);
|
||||
dispatch({
|
||||
type: actionConstants.SET_SMART_TRANSACTIONS_ERROR,
|
||||
payload: errorObj.type,
|
||||
payload: errorObj,
|
||||
});
|
||||
}
|
||||
throw e;
|
||||
@ -3647,7 +3657,7 @@ export function signAndSendSmartTransaction({
|
||||
const errorObj = parseSmartTransactionsError(e.message);
|
||||
dispatch({
|
||||
type: actionConstants.SET_SMART_TRANSACTIONS_ERROR,
|
||||
payload: errorObj.type,
|
||||
payload: errorObj,
|
||||
});
|
||||
}
|
||||
throw e;
|
||||
@ -3668,7 +3678,7 @@ export function updateSmartTransaction(uuid, txData) {
|
||||
const errorObj = parseSmartTransactionsError(e.message);
|
||||
dispatch({
|
||||
type: actionConstants.SET_SMART_TRANSACTIONS_ERROR,
|
||||
payload: errorObj.type,
|
||||
payload: errorObj,
|
||||
});
|
||||
}
|
||||
throw e;
|
||||
@ -3696,7 +3706,7 @@ export function cancelSmartTransaction(uuid) {
|
||||
const errorObj = parseSmartTransactionsError(e.message);
|
||||
dispatch({
|
||||
type: actionConstants.SET_SMART_TRANSACTIONS_ERROR,
|
||||
payload: errorObj.type,
|
||||
payload: errorObj,
|
||||
});
|
||||
}
|
||||
throw e;
|
||||
|
@ -3124,10 +3124,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@metamask/slip44/-/slip44-2.1.0.tgz#f76764ca54afc162fbfe563f1994b79ed4711bba"
|
||||
integrity sha512-wkFDdY4XtpF+XCqbgwhsrLRgEM/bYfIt47927JTQZQ2QxQYRbSZ6u0QygnVjIR1eqMteRGx2jtUUZ+bxYQTo/w==
|
||||
|
||||
"@metamask/smart-transactions-controller@^2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/smart-transactions-controller/-/smart-transactions-controller-2.1.0.tgz#a1bfbdab05c0ecd93bac5587b8bc56336bc28cc5"
|
||||
integrity sha512-nhvR44ELv/8iBVHaMT8D6B1KLwm9PAb7BSSLAiteDfncxHj7syNGD0nOFynzKp7TPQuRlPXdFo6Z8zXt6O/krg==
|
||||
"@metamask/smart-transactions-controller@^2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@metamask/smart-transactions-controller/-/smart-transactions-controller-2.3.0.tgz#8e451975fbfc624f7cd55b2d1bc406f78fe95119"
|
||||
integrity sha512-ef8RolP/synZZ9RVMaRAApZmiUbRlAAs0Pt3u/R0+fXeB0NTdUljQ7TfKa4kfulcxW7EbSnJ7kNabeBinyE4vw==
|
||||
dependencies:
|
||||
"@metamask/controllers" "^30.0.0"
|
||||
"@types/lodash" "^4.14.176"
|
||||
|
Loading…
Reference in New Issue
Block a user