Merge pull request #15454 from MetaMask/Version-v10.18.2

Version v10.18.2 RC
feature/default_network_editable
Dan J Miller 2 years ago committed by GitHub
commit 11763a1fcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      CHANGELOG.md
  2. 21
      app/scripts/controllers/transactions/index.js
  3. 2
      package.json
  4. 3
      ui/components/app/user-preferenced-currency-display/user-preferenced-currency-display.component.js
  5. 197
      ui/ducks/send/send.js
  6. 67
      ui/ducks/send/send.test.js
  7. 7
      ui/helpers/utils/transactions.util.js
  8. 10
      ui/hooks/useTokenDisplayValue.js
  9. 73
      ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
  10. 13
      ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.test.js
  11. 6
      ui/pages/confirm-approve/confirm-approve-content/index.scss
  12. 8
      ui/pages/confirm-transaction-base/confirm-transaction-base.component.js
  13. 11
      ui/pages/send/send-footer/send-footer.component.js
  14. 18
      ui/pages/send/send-footer/send-footer.container.js
  15. 6
      ui/pages/send/send.constants.js
  16. 6
      ui/pages/settings/networks-tab/networks-list-item/networks-list-item.js
  17. 2
      ui/selectors/selectors.js
  18. 8
      ui/store/actions.js

@ -6,6 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [10.18.2]
### Changed
- Enhance approval screen title logic ([#15406](https://github.com/MetaMask/metamask-extension/pull/15406))
### Fixed
- Ensure smart contract interactions are properly represented on the confirm screen ([#15446](https://github.com/MetaMask/metamask-extension/pull/15446))
- Fix update of max amount in send flow after network switch([#15444](https://github.com/MetaMask/metamask-extension/pull/15444))
- Fix to ensure user can access full screen editing of network forms from the popup ([#15442](https://github.com/MetaMask/metamask-extension/pull/15442))
- Possibly fix bug which crashes firefox on startup after upgrade to v10.18.1 ([#15425](https://github.com/MetaMask/metamask-extension/pull/15425))
- Fix blocking of editing transactions that had a contract address recipient but no tx data ([#15424](https://github.com/MetaMask/metamask-extension/pull/15424))
- Fix error that could leave the app in a stuck state when quickly moving between the send screen and other screens ([#15420](https://github.com/MetaMask/metamask-extension/pull/15420))
- Fix send screen for Optimism network ([#15419](https://github.com/MetaMask/metamask-extension/pull/15419))
- Fix to ensure the correct balance is used when validating send amounts ([#15449](https://github.com/MetaMask/metamask-extension/pull/15449))
- Fix error that makes app unusable after clicking activity list items for token approval transactions ([#15398](https://github.com/MetaMask/metamask-extension/pull/15398))
## [10.18.1]
### Changed
- Move the metrics opt-in screen to the second screen of the onboarding flow ([#15313](https://github.com/MetaMask/metamask-extension/pull/15313))
@ -3088,7 +3103,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Uncategorized
- Added the ability to restore accounts from seed words.
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.18.1...HEAD
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v10.18.2...HEAD
[10.18.2]: https://github.com/MetaMask/metamask-extension/compare/v10.18.1...v10.18.2
[10.18.1]: https://github.com/MetaMask/metamask-extension/compare/v10.18.0...v10.18.1
[10.18.0]: https://github.com/MetaMask/metamask-extension/compare/v10.17.0...v10.18.0
[10.17.0]: https://github.com/MetaMask/metamask-extension/compare/v10.16.2...v10.17.0

@ -17,7 +17,6 @@ import {
addHexPrefix,
getChainType,
} from '../../lib/util';
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/helpers/constants/error-keys';
import { calcGasTotal } from '../../../../ui/pages/send/send.utils';
import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/pages/swaps/swaps.util';
import {
@ -75,6 +74,7 @@ const VALID_UNAPPROVED_TRANSACTION_TYPES = [
TRANSACTION_TYPES.SIMPLE_SEND,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
TRANSACTION_TYPES.CONTRACT_INTERACTION,
];
/**
@ -1006,10 +1006,9 @@ export default class TransactionController extends EventEmitter {
* Gets default gas limit, or debug information about why gas estimate failed.
*
* @param {Object} txMeta - The txMeta object
* @param {string} getCodeResponse - The transaction category code response, used for debugging purposes
* @returns {Promise<Object>} Object containing the default gas limit, or the simulation failure object
*/
async _getDefaultGasLimit(txMeta, getCodeResponse) {
async _getDefaultGasLimit(txMeta) {
const chainId = this._getCurrentChainId();
const customNetworkGasBuffer = CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP[chainId];
const chainType = getChainType(chainId);
@ -1019,21 +1018,9 @@ export default class TransactionController extends EventEmitter {
} else if (
txMeta.txParams.to &&
txMeta.type === TRANSACTION_TYPES.SIMPLE_SEND &&
chainType !== 'custom'
chainType !== 'custom' &&
!txMeta.txParams.data
) {
// if there's data in the params, but there's no contract code, it's not a valid transaction
if (txMeta.txParams.data) {
const err = new Error(
'TxGasUtil - Trying to call a function on a non-contract address',
);
// set error key so ui can display localized error message
err.errorKey = TRANSACTION_NO_CONTRACT_ERROR_KEY;
// set the response on the error so that we can see in logs what the actual response was
err.getCodeResponse = getCodeResponse;
throw err;
}
// This is a standard ether simple send, gas requirement is exactly 21k
return { gasLimit: GAS_LIMITS.SIMPLE };
}

@ -1,6 +1,6 @@
{
"name": "metamask-crx",
"version": "10.18.1",
"version": "10.18.2",
"private": true,
"repository": {
"type": "git",

@ -13,6 +13,7 @@ export default function UserPreferencedCurrencyDisplay({
showEthLogo,
type,
showFiat,
showCurrencySuffix,
...restProps
}) {
const { currency, numberOfDecimals } = useUserPreferencedCurrency(type, {
@ -43,6 +44,7 @@ export default function UserPreferencedCurrencyDisplay({
data-testid={dataTestId}
numberOfDecimals={numberOfDecimals}
prefixComponent={prefixComponent}
suffix={showCurrencySuffix && !showEthLogo && currency}
/>
);
}
@ -68,4 +70,5 @@ UserPreferencedCurrencyDisplay.propTypes = {
PropTypes.number,
]),
showFiat: PropTypes.bool,
showCurrencySuffix: PropTypes.bool,
};

@ -18,6 +18,7 @@ import {
INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR,
KNOWN_RECIPIENT_ADDRESS_WARNING,
NEGATIVE_ETH_ERROR,
RECIPIENT_TYPES,
} from '../../pages/send/send.constants';
import {
@ -82,6 +83,10 @@ import {
getTokens,
getUnapprovedTxs,
} from '../metamask/metamask';
import {
isSmartContractAddress,
sumHexes,
} from '../../helpers/utils/transactions.util';
import { resetEnsResolution } from '../ens';
import {
@ -89,7 +94,7 @@ import {
isValidHexAddress,
toChecksumHexAddress,
} from '../../../shared/modules/hexstring-utils';
import { sumHexes } from '../../helpers/utils/transactions.util';
import fetchEstimatedL1Fee from '../../helpers/utils/optimism/fetchEstimatedL1Fee';
import { TOKEN_STANDARDS, ETH } from '../../helpers/constants/common';
@ -383,6 +388,7 @@ export const draftTransactionInitialState = {
error: null,
nickname: '',
warning: null,
type: '',
recipientWarningAcknowledged: false,
},
status: SEND_STATUSES.VALID,
@ -513,7 +519,7 @@ export const computeEstimatedGasLimit = createAsyncThunk(
value:
send.amountMode === AMOUNT_MODES.MAX
? send.selectedAccount.balance
: send.amount.value,
: draftTransaction.amount.value,
from: send.selectedAccount.address,
data: draftTransaction.userInputHexData,
type: '0x0',
@ -598,7 +604,7 @@ export const initializeSendState = createAsyncThunk(
// For instance, in the actions.js file we dispatch this action anytime the
// chain changes.
if (!draftTransaction) {
thunkApi.rejectWithValue(
return thunkApi.rejectWithValue(
'draftTransaction not found, possibly not on send flow',
);
}
@ -673,6 +679,20 @@ export const initializeSendState = createAsyncThunk(
// We have to keep the gas slice in sync with the send slice state
// so that it'll be initialized correctly if the gas modal is opened.
await thunkApi.dispatch(setCustomGasLimit(gasLimit));
// There may be a case where the send has been canceled by the user while
// the gas estimate is being computed. So we check again to make sure that
// a currentTransactionUUID exists and matches the previous tx.
const newState = thunkApi.getState();
if (
newState.send.currentTransactionUUID !== sendState.currentTransactionUUID
) {
return thunkApi.rejectWithValue(
`draftTransaction changed during initialization.
A new initializeSendState action must be dispatched.`,
);
}
return {
account,
chainId: getCurrentChainId(state),
@ -1158,6 +1178,12 @@ const slice = createSlice({
draftTransaction.recipient.warning = action.payload;
},
updateRecipientType: (state, action) => {
const draftTransaction =
state.draftTransactions[state.currentTransactionUUID];
draftTransaction.recipient.type = action.payload;
},
updateDraftTransactionStatus: (state, action) => {
const draftTransaction =
state.draftTransactions[state.currentTransactionUUID];
@ -1398,56 +1424,60 @@ const slice = createSlice({
validateSendState: (state) => {
const draftTransaction =
state.draftTransactions[state.currentTransactionUUID];
switch (true) {
case Boolean(draftTransaction.amount.error):
case Boolean(draftTransaction.gas.error):
case Boolean(draftTransaction.asset.error):
case draftTransaction.asset.type === ASSET_TYPES.TOKEN &&
draftTransaction.asset.details === null:
case state.stage === SEND_STAGES.ADD_RECIPIENT:
case state.stage === SEND_STAGES.INACTIVE:
case state.gasEstimateIsLoading:
case new BigNumber(draftTransaction.gas.gasLimit, 16).lessThan(
new BigNumber(state.gasLimitMinimum),
):
draftTransaction.status = SEND_STATUSES.INVALID;
break;
case draftTransaction.recipient.warning === 'loading':
case draftTransaction.recipient.warning ===
KNOWN_RECIPIENT_ADDRESS_WARNING &&
draftTransaction.recipient.recipientWarningAcknowledged === false:
draftTransaction.status = SEND_STATUSES.INVALID;
break;
default:
draftTransaction.status = SEND_STATUSES.VALID;
if (draftTransaction) {
switch (true) {
case Boolean(draftTransaction.amount.error):
case Boolean(draftTransaction.gas.error):
case Boolean(draftTransaction.asset.error):
case draftTransaction.asset.type === ASSET_TYPES.TOKEN &&
draftTransaction.asset.details === null:
case state.stage === SEND_STAGES.ADD_RECIPIENT:
case state.stage === SEND_STAGES.INACTIVE:
case state.gasEstimateIsLoading:
case new BigNumber(draftTransaction.gas.gasLimit, 16).lessThan(
new BigNumber(state.gasLimitMinimum),
):
draftTransaction.status = SEND_STATUSES.INVALID;
break;
case draftTransaction.recipient.warning === 'loading':
case draftTransaction.recipient.warning ===
KNOWN_RECIPIENT_ADDRESS_WARNING &&
draftTransaction.recipient.recipientWarningAcknowledged === false:
draftTransaction.status = SEND_STATUSES.INVALID;
break;
default:
draftTransaction.status = SEND_STATUSES.VALID;
}
}
},
},
extraReducers: (builder) => {
builder
.addCase(ACCOUNT_CHANGED, (state, action) => {
// If we are on the edit flow then we need to watch for changes to the
// current account.address in state and keep balance updated
// appropriately
if (
state.stage === SEND_STAGES.EDIT &&
action.payload.account.address === state.selectedAccount.address
) {
// This event occurs when the user's account details update due to
// background state changes. If the account that is being updated is
// the current from account on the edit flow we need to update
// the balance for the account and revalidate the send state.
state.selectedAccount.balance = action.payload.account.balance;
// We need to update the asset balance if the asset is the native
// network asset. Once we update the balance we recompute error state.
// This event occurs when the user's account details update due to
// background state changes. If the account that is being updated is
// the current from account on the edit flow we need to update
// the balance for the account and revalidate the send state.
if (state.stage === SEND_STAGES.EDIT && action.payload.account) {
const draftTransaction =
state.draftTransactions[state.currentTransactionUUID];
if (draftTransaction?.asset.type === ASSET_TYPES.NATIVE) {
draftTransaction.asset.balance = action.payload.account.balance;
if (
draftTransaction &&
draftTransaction.fromAccount &&
draftTransaction.fromAccount.address ===
action.payload.account.address
) {
draftTransaction.fromAccount.balance =
action.payload.account.balance;
// We need to update the asset balance if the asset is the native
// network asset. Once we update the balance we recompute error state.
if (draftTransaction.asset.type === ASSET_TYPES.NATIVE) {
draftTransaction.asset.balance = action.payload.account.balance;
}
slice.caseReducers.validateAmountField(state);
slice.caseReducers.validateGasField(state);
slice.caseReducers.validateSendState(state);
}
slice.caseReducers.validateAmountField(state);
slice.caseReducers.validateGasField(state);
slice.caseReducers.validateSendState(state);
}
})
.addCase(ADDRESS_BOOK_UPDATED, (state, action) => {
@ -1514,26 +1544,28 @@ const slice = createSlice({
state.selectedAccount.balance = action.payload.account.balance;
const draftTransaction =
state.draftTransactions[state.currentTransactionUUID];
draftTransaction.gas.gasLimit = action.payload.gasLimit;
if (draftTransaction) {
draftTransaction.gas.gasLimit = action.payload.gasLimit;
draftTransaction.gas.gasTotal = action.payload.gasTotal;
if (action.payload.chainHasChanged) {
// If the state was reinitialized as a result of the user changing
// the network from the network dropdown, then the selected asset is
// no longer valid and should be set to the native asset for the
// network.
draftTransaction.asset.type = ASSET_TYPES.NATIVE;
draftTransaction.asset.balance =
draftTransaction.fromAccount?.balance ??
state.selectedAccount.balance;
draftTransaction.asset.details = null;
}
}
slice.caseReducers.updateGasFeeEstimates(state, {
payload: {
gasFeeEstimates: action.payload.gasFeeEstimates,
gasEstimateType: action.payload.gasEstimateType,
},
});
draftTransaction.gas.gasTotal = action.payload.gasTotal;
state.gasEstimatePollToken = action.payload.gasEstimatePollToken;
if (action.payload.chainHasChanged) {
// If the state was reinitialized as a result of the user changing
// the network from the network dropdown, then the selected asset is
// no longer valid and should be set to the native asset for the
// network.
draftTransaction.asset.type = ASSET_TYPES.NATIVE;
draftTransaction.asset.balance =
draftTransaction.fromAccount?.balance ??
state.selectedAccount.balance;
draftTransaction.asset.details = null;
}
if (action.payload.gasEstimatePollToken) {
state.gasEstimateIsLoading = false;
}
@ -1547,16 +1579,19 @@ const slice = createSlice({
},
});
}
if (state.amountMode === AMOUNT_MODES.MAX) {
slice.caseReducers.updateAmountToMax(state);
}
slice.caseReducers.validateAmountField(state);
slice.caseReducers.validateGasField(state);
slice.caseReducers.validateSendState(state);
})
.addCase(SELECTED_ACCOUNT_CHANGED, (state, action) => {
// If we are on the edit flow the account we are keyed into will be the
// original 'from' account, which may differ from the selected account
if (state.stage !== SEND_STAGES.EDIT) {
// This event occurs when the user selects a new account from the
// account menu, or the currently active account's balance updates.
// This event occurs when the user selects a new account from the
// account menu, or the currently active account's balance updates.
// We only care about new transactions, not edits, here, because we use
// the fromAccount and ACCOUNT_CHANGED action for that.
if (state.stage !== SEND_STAGES.EDIT && action.payload.account) {
state.selectedAccount.balance = action.payload.account.balance;
state.selectedAccount.address = action.payload.account.address;
const draftTransaction =
@ -1855,19 +1890,24 @@ export function updateRecipientUserInput(userInput) {
const inputIsValidHexAddress = isValidHexAddress(userInput);
let isProbablyAnAssetContract = false;
if (inputIsValidHexAddress) {
const { symbol, decimals } = getTokenMetadata(userInput, tokenMap) || {};
const smartContractAddress = await isSmartContractAddress(userInput);
if (smartContractAddress) {
dispatch(actions.updateRecipientType(RECIPIENT_TYPES.SMART_CONTRACT));
const { symbol, decimals } =
getTokenMetadata(userInput, tokenMap) || {};
isProbablyAnAssetContract = symbol && decimals !== undefined;
isProbablyAnAssetContract = symbol && decimals !== undefined;
if (!isProbablyAnAssetContract) {
try {
const { standard } = await getTokenStandardAndDetails(
userInput,
sendingAddress,
);
isProbablyAnAssetContract = Boolean(standard);
} catch (e) {
console.log(e);
if (!isProbablyAnAssetContract) {
try {
const { standard } = await getTokenStandardAndDetails(
userInput,
sendingAddress,
);
isProbablyAnAssetContract = Boolean(standard);
} catch (e) {
console.log(e);
}
}
}
}
@ -2238,7 +2278,10 @@ export function signTransaction() {
updateTransactionGasFees(draftTransaction.id, editingTx.txParams),
);
} else {
let transactionType = TRANSACTION_TYPES.SIMPLE_SEND;
let transactionType =
draftTransaction.recipient.type === RECIPIENT_TYPES.SMART_CONTRACT
? TRANSACTION_TYPES.CONTRACT_INTERACTION
: TRANSACTION_TYPES.SIMPLE_SEND;
if (draftTransaction.asset.type !== ASSET_TYPES.NATIVE) {
transactionType =
@ -2630,7 +2673,11 @@ export function isSendStateInitialized(state) {
* @type {Selector<boolean>}
*/
export function isSendFormInvalid(state) {
return getCurrentDraftTransaction(state).status === SEND_STATUSES.INVALID;
const draftTransaction = getCurrentDraftTransaction(state);
if (!draftTransaction) {
return true;
}
return draftTransaction.status === SEND_STATUSES.INVALID;
}
/**

@ -1129,12 +1129,39 @@ describe('Send Slice', () => {
action.payload.account.address,
);
});
it('should gracefully handle missing account in payload', () => {
const olderState = {
...INITIAL_SEND_STATE_FOR_EXISTING_DRAFT,
selectedAccount: {
balance: '0x0',
address: '0xAddress',
},
};
const action = {
type: 'SELECTED_ACCOUNT_CHANGED',
payload: {
account: undefined,
},
};
const result = sendReducer(olderState, action);
expect(result.selectedAccount.balance).toStrictEqual('0x0');
expect(result.selectedAccount.address).toStrictEqual('0xAddress');
});
});
describe('Account Changed', () => {
it('should', () => {
it('should correctly update the fromAccount in an edit', () => {
const accountsChangedState = {
...INITIAL_SEND_STATE_FOR_EXISTING_DRAFT,
...getInitialSendStateWithExistingTxState({
fromAccount: {
address: '0xAddress',
balance: '0x0',
},
}),
stage: SEND_STAGES.EDIT,
selectedAccount: {
address: '0xAddress',
@ -1154,11 +1181,42 @@ describe('Send Slice', () => {
const result = sendReducer(accountsChangedState, action);
expect(result.selectedAccount.balance).toStrictEqual(
const draft = getTestUUIDTx(result);
expect(draft.fromAccount.balance).toStrictEqual(
action.payload.account.balance,
);
});
it('should gracefully handle missing account param in payload', () => {
const accountsChangedState = {
...getInitialSendStateWithExistingTxState({
fromAccount: {
address: '0xAddress',
balance: '0x0',
},
}),
stage: SEND_STAGES.EDIT,
selectedAccount: {
address: '0xAddress',
balance: '0x0',
},
};
const action = {
type: 'ACCOUNT_CHANGED',
payload: {
account: undefined,
},
};
const result = sendReducer(accountsChangedState, action);
const draft = getTestUUIDTx(result);
expect(draft.fromAccount.balance).toStrictEqual('0x0');
});
it(`should not edit account balance if action payload address is not the same as state's address`, () => {
const accountsChangedState = {
...INITIAL_SEND_STATE_FOR_EXISTING_DRAFT,
@ -2390,6 +2448,7 @@ describe('Send Slice', () => {
nickname: '',
warning: null,
recipientWarningAcknowledged: false,
type: '',
},
status: SEND_STATUSES.VALID,
transactionType: '0x0',
@ -2532,6 +2591,7 @@ describe('Send Slice', () => {
error: null,
nickname: '',
warning: null,
type: '',
recipientWarningAcknowledged: false,
},
status: SEND_STATUSES.VALID,
@ -2718,6 +2778,7 @@ describe('Send Slice', () => {
error: null,
warning: null,
nickname: '',
type: '',
recipientWarningAcknowledged: false,
},
status: SEND_STATUSES.VALID,

@ -144,8 +144,11 @@ export function getLatestSubmittedTxWithNonce(
}
export async function isSmartContractAddress(address) {
const { isContractCode } = await readAddressAsContract(global.eth, address);
return isContractCode;
const { isContractAddress } = await readAddressAsContract(
global.eth,
address,
);
return isContractAddress;
}
export function sumHexes(...args) {

@ -35,6 +35,8 @@ export function useTokenDisplayValue(
isTokenTransaction = true,
) {
const tokenData = useTokenData(transactionData, isTokenTransaction);
const tokenValue = getTokenValueParam(tokenData);
const shouldCalculateTokenValue = Boolean(
// If we are currently processing a token transaction
isTokenTransaction &&
@ -42,15 +44,17 @@ export function useTokenDisplayValue(
transactionData &&
// and a token object has been provided
token &&
// and we are able to parse the token details from the raw data
tokenData?.args?.length,
// and the provided token object contains a defined decimal value we need to calculate amount
token.decimals &&
// and we are able to parse the token detail we to calculate amount from the raw data
tokenValue,
);
const displayValue = useMemo(() => {
if (!shouldCalculateTokenValue) {
return null;
}
const tokenValue = getTokenValueParam(tokenData);
return calcTokenAmount(tokenValue, token.decimals).toString(10);
}, [shouldCalculateTokenValue, tokenData, token]);

@ -29,6 +29,10 @@ import { ConfirmPageContainerWarning } from '../../../components/app/confirm-pag
import GasDetailsItem from '../../../components/app/gas-details-item';
import LedgerInstructionField from '../../../components/app/ledger-instruction-field';
import { ERC1155, ERC20, ERC721 } from '../../../helpers/constants/common';
import {
MAINNET_CHAIN_ID,
TEST_CHAINS,
} from '../../../../shared/constants/network';
export default class ConfirmApproveContent extends Component {
static contextTypes = {
@ -458,31 +462,12 @@ export default class ConfirmApproveContent extends Component {
userAddress,
} = this.props;
const { t } = this.context;
let titleTokenDescription = t('token');
if (rpcPrefs?.blockExplorerUrl || chainId) {
const unknownTokenBlockExplorerLink = getTokenTrackerLink(
tokenAddress,
chainId,
null,
userAddress,
{
blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null,
},
);
const unknownTokenLink = (
<a
href={unknownTokenBlockExplorerLink}
target="_blank"
rel="noopener noreferrer"
className="confirm-approve-content__unknown-asset"
>
{t('token')}
</a>
);
titleTokenDescription = unknownTokenLink;
}
const useBlockExplorer =
rpcPrefs?.blockExplorerUrl ||
[...TEST_CHAINS, MAINNET_CHAIN_ID].includes(chainId);
let titleTokenDescription = t('token');
const tokenIdWrapped = tokenId ? ` (#${tokenId})` : '';
if (
assetStandard === ERC20 ||
(tokenSymbol && !tokenId && !isSetApproveForAll)
@ -495,11 +480,14 @@ export default class ConfirmApproveContent extends Component {
(assetName && tokenId) ||
(tokenSymbol && tokenId)
) {
const tokenIdWrapped = tokenId ? ` (#${tokenId})` : '';
if (assetName || tokenSymbol) {
titleTokenDescription = `${assetName ?? tokenSymbol}${tokenIdWrapped}`;
titleTokenDescription = `${assetName ?? tokenSymbol}`;
} else {
const unknownNFTBlockExplorerLink = getTokenTrackerLink(
titleTokenDescription = t('nft');
}
if (useBlockExplorer) {
const blockExplorerLink = getTokenTrackerLink(
tokenAddress,
chainId,
null,
@ -508,24 +496,38 @@ export default class ConfirmApproveContent extends Component {
blockExplorerUrl: rpcPrefs?.blockExplorerUrl ?? null,
},
);
const unknownNFTLink = (
const blockExplorerElement = (
<>
<a
href={unknownNFTBlockExplorerLink}
href={blockExplorerLink}
target="_blank"
rel="noopener noreferrer"
className="confirm-approve-content__unknown-asset"
title={tokenAddress}
className="confirm-approve-content__approval-asset-link"
>
{t('nft')}
{titleTokenDescription}
</a>
{tokenIdWrapped && <span>{tokenIdWrapped}</span>}
</>
);
titleTokenDescription = unknownNFTLink;
return blockExplorerElement;
}
}
return titleTokenDescription;
return (
<>
<span
className="confirm-approve-content__approval-asset-title"
onClick={() => {
copyToClipboard(tokenAddress);
}}
title={tokenAddress}
>
{titleTokenDescription}
</span>
{tokenIdWrapped && <span>{tokenIdWrapped}</span>}
</>
);
}
renderTitle() {
@ -627,7 +629,10 @@ export default class ConfirmApproveContent extends Component {
</Typography>
</Box>
</Box>
<div className="confirm-approve-content__title">
<div
className="confirm-approve-content__title"
data-testid="confirm-approve-title"
>
{this.renderTitle()}
</div>
<div className="confirm-approve-content__description">

@ -40,11 +40,16 @@ const props = {
describe('ConfirmApproveContent Component', () => {
it('should render Confirm approve page correctly', () => {
const { queryByText, getByText, getAllByText } = renderComponent(props);
const {
queryByText,
getByText,
getAllByText,
getByTestId,
} = renderComponent(props);
expect(queryByText('metamask.github.io')).toBeInTheDocument();
expect(
queryByText('Give permission to access your TST?'),
).toBeInTheDocument();
expect(getByTestId('confirm-approve-title').textContent).toBe(
' Give permission to access your TST? ',
);
expect(
queryByText(
'By granting permission, you are allowing the following contract to access your funds',

@ -9,10 +9,14 @@
padding: 0 24px 16px 24px;
}
&__unknown-asset {
&__approval-asset-link {
color: var(--color-primary-default);
}
&__approval-asset-title {
cursor: pointer;
}
&__icon-display-content {
display: flex;
height: 51px;

@ -864,20 +864,24 @@ export default class ConfirmTransactionBase extends Component {
}
renderTitleComponent() {
const { title, hexTransactionAmount } = this.props;
const { title, hexTransactionAmount, txData } = this.props;
// Title string passed in by props takes priority
if (title) {
return null;
}
const isContractInteraction =
txData.type === TRANSACTION_TYPES.CONTRACT_INTERACTION;
return (
<UserPreferencedCurrencyDisplay
value={hexTransactionAmount}
type={PRIMARY}
showEthLogo
ethLogoHeight={24}
hideLabel
hideLabel={!isContractInteraction}
showCurrencySuffix={isContractInteraction}
/>
);
}

@ -20,7 +20,6 @@ export default class SendFooter extends Component {
toAccounts: PropTypes.array,
sendStage: PropTypes.string,
sendErrors: PropTypes.object,
gasEstimateType: PropTypes.string,
mostRecentOverviewPage: PropTypes.string.isRequired,
cancelTx: PropTypes.func,
draftTransactionID: PropTypes.string,
@ -53,14 +52,7 @@ export default class SendFooter extends Component {
async onSubmit(event) {
event.preventDefault();
const {
addToAddressBookIfNew,
sign,
to,
toAccounts,
history,
gasEstimateType,
} = this.props;
const { addToAddressBookIfNew, sign, to, toAccounts, history } = this.props;
const { trackEvent } = this.context;
// TODO: add nickname functionality
@ -74,7 +66,6 @@ export default class SendFooter extends Component {
properties: {
action: 'Edit Screen',
legacy_event: true,
gasChanged: gasEstimateType,
},
});
history.push(CONFIRM_TRANSACTION_ROUTE);

@ -1,12 +1,7 @@
import { connect } from 'react-redux';
import { addToAddressBook, cancelTx } from '../../../store/actions';
import {
getRenderableEstimateDataForSmallButtonsFromGWEI,
getDefaultActiveButtonIndex,
} from '../../../selectors';
import {
resetSendState,
getGasPrice,
getSendStage,
getSendTo,
getSendErrors,
@ -17,7 +12,6 @@ import {
import { getMostRecentOverviewPage } from '../../../ducks/history/history';
import { addHexPrefix } from '../../../../app/scripts/lib/util';
import { getSendToAccounts } from '../../../ducks/metamask/metamask';
import { CUSTOM_GAS_ESTIMATE } from '../../../../shared/constants/gas';
import SendFooter from './send-footer.component';
export default connect(mapStateToProps, mapDispatchToProps)(SendFooter);
@ -31,17 +25,6 @@ function addressIsNew(toAccounts, newAddress) {
}
function mapStateToProps(state) {
const gasButtonInfo = getRenderableEstimateDataForSmallButtonsFromGWEI(state);
const gasPrice = getGasPrice(state);
const activeButtonIndex = getDefaultActiveButtonIndex(
gasButtonInfo,
gasPrice,
);
const gasEstimateType =
activeButtonIndex >= 0
? gasButtonInfo[activeButtonIndex].gasEstimateType
: CUSTOM_GAS_ESTIMATE;
return {
disabled: isSendFormInvalid(state),
to: getSendTo(state),
@ -49,7 +32,6 @@ function mapStateToProps(state) {
sendStage: getSendStage(state),
sendErrors: getSendErrors(state),
draftTransactionID: getDraftTransactionID(state),
gasEstimateType,
mostRecentOverviewPage: getMostRecentOverviewPage(state),
};
}

@ -47,6 +47,11 @@ const ENS_ILLEGAL_CHARACTER = 'ensIllegalCharacter';
const ENS_UNKNOWN_ERROR = 'ensUnknownError';
const ENS_REGISTRATION_ERROR = 'ensRegistrationError';
const RECIPIENT_TYPES = {
SMART_CONTRACT: 'SMART_CONTRACT',
NON_CONTRACT: 'NON_CONTRACT',
};
export {
MAX_GAS_LIMIT_DEC,
HIGH_FEE_WARNING_MULTIPLIER,
@ -73,4 +78,5 @@ export {
CONFUSING_ENS_ERROR,
TOKEN_TRANSFER_FUNCTION_SIGNATURE,
COLLECTIBLE_TRANSFER_FROM_FUNCTION_SIGNATURE,
RECIPIENT_TYPES,
};

@ -2,7 +2,6 @@ import React, { useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router-dom';
import { useI18nContext } from '../../../../hooks/useI18nContext';
import {
NETWORK_TYPE_RPC,
@ -10,7 +9,7 @@ import {
} from '../../../../../shared/constants/network';
import LockIcon from '../../../../components/ui/lock-icon';
import IconCheck from '../../../../components/ui/icon/icon-check';
import { NETWORKS_FORM_ROUTE } from '../../../../helpers/constants/routes';
import { NETWORKS_ROUTE } from '../../../../helpers/constants/routes';
import { setSelectedSettingsRpcUrl } from '../../../../store/actions';
import { getEnvironmentType } from '../../../../../app/scripts/lib/util';
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../../shared/constants/app';
@ -28,7 +27,6 @@ const NetworksListItem = ({
setSearchedNetworks,
}) => {
const t = useI18nContext();
const history = useHistory();
const dispatch = useDispatch();
const environmentType = getEnvironmentType();
const isFullScreen = environmentType === ENVIRONMENT_TYPE_FULLSCREEN;
@ -68,7 +66,7 @@ const NetworksListItem = ({
setSearchedNetworks([]);
dispatch(setSelectedSettingsRpcUrl(rpcUrl));
if (!isFullScreen) {
history.push(NETWORKS_FORM_ROUTE);
global.platform.openExtensionInBrowser(NETWORKS_ROUTE);
}
}}
>

@ -725,7 +725,7 @@ export function getIsBuyableCoinbasePayChain(state) {
}
export function getNativeCurrencyImage(state) {
const nativeCurrency = getNativeCurrency(state).toUpperCase();
const nativeCurrency = getNativeCurrency(state)?.toUpperCase();
return NATIVE_CURRENCY_TOKEN_IMAGE_MAP[nativeCurrency];
}

@ -1442,6 +1442,10 @@ export function updateMetamaskState(newState) {
},
});
}
dispatch({
type: actionConstants.UPDATE_METAMASK_STATE,
value: newState,
});
if (provider.chainId !== newProvider.chainId) {
dispatch({
type: actionConstants.CHAIN_CHANGED,
@ -1453,10 +1457,6 @@ export function updateMetamaskState(newState) {
// progress.
dispatch(initializeSendState({ chainHasChanged: true }));
}
dispatch({
type: actionConstants.UPDATE_METAMASK_STATE,
value: newState,
});
};
}

Loading…
Cancel
Save