mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
Merge pull request #15454 from MetaMask/Version-v10.18.2
Version v10.18.2 RC
This commit is contained in:
commit
11763a1fcb
18
CHANGELOG.md
18
CHANGELOG.md
@ -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;
|
||||
const useBlockExplorer =
|
||||
rpcPrefs?.blockExplorerUrl ||
|
||||
[...TEST_CHAINS, MAINNET_CHAIN_ID].includes(chainId);
|
||||
|
||||
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 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…
Reference in New Issue
Block a user