diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index e7fe44c60..78bb505b8 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -230,6 +230,10 @@
"alerts": {
"message": "Alerts"
},
+ "allOfYour": {
+ "message": "All of your $1",
+ "description": "$1 is the symbol or name of the token that the user is approving spending"
+ },
"allowExternalExtensionTo": {
"message": "Allow this external extension to:"
},
@@ -266,6 +270,10 @@
"approve": {
"message": "Approve spend limit"
},
+ "approveAllTokensTitle": {
+ "message": "Give permission to access all of your $1?",
+ "description": "$1 is the symbol of the token for which the user is granting approval"
+ },
"approveAndInstall": {
"message": "Approve & Install"
},
@@ -1287,6 +1295,9 @@
"functionApprove": {
"message": "Function: Approve"
},
+ "functionSetApprovalForAll": {
+ "message": "Function: SetApprovalForAll"
+ },
"functionType": {
"message": "Function Type"
},
@@ -2696,6 +2707,14 @@
"revealTheSeedPhrase": {
"message": "Reveal seed phrase"
},
+ "revokeAllTokensTitle": {
+ "message": "Revoke permission to access all of your $1?",
+ "description": "$1 is the symbol of the token for which the user is revoking approval"
+ },
+ "revokeApproveForAllDescription": {
+ "message": "By revoking permission, the following $1 will no longer be able to access your $2",
+ "description": "$1 is either key 'account' or 'contract', and $2 is either a string or link of a given token symbol or name"
+ },
"rinkeby": {
"message": "Rinkeby Test Network"
},
@@ -2878,6 +2897,13 @@
"setAdvancedPrivacySettingsDetails": {
"message": "MetaMask uses these trusted third-party services to enhance product usability and safety."
},
+ "setApprovalForAll": {
+ "message": "Set Approval for All"
+ },
+ "setApprovalForAllTitle": {
+ "message": "Approve $1 with no spend limit",
+ "description": "The token symbol that is being approved"
+ },
"settings": {
"message": "Settings"
},
diff --git a/shared/constants/transaction.js b/shared/constants/transaction.js
index 8cb365f79..d2a54ae35 100644
--- a/shared/constants/transaction.js
+++ b/shared/constants/transaction.js
@@ -15,6 +15,8 @@ import { MESSAGE_TYPE } from './app';
* to ensure that the receiver is an address capable of handling with the token being sent.
* @property {'approve'} TOKEN_METHOD_APPROVE - A token transaction requesting an
* allowance of the token to spend on behalf of the user
+ * @property {'setapprovalforall'} TOKEN_METHOD_SET_APPROVAL_FOR_ALL - A token transaction requesting an
+ * allowance of all of a user's token to spend on behalf of the user
* @property {'incoming'} INCOMING - An incoming (deposit) transaction
* @property {'simpleSend'} SIMPLE_SEND - A transaction sending a network's native asset to a recipient
* @property {'contractInteraction'} CONTRACT_INTERACTION - A transaction that is
@@ -66,6 +68,7 @@ export const TRANSACTION_TYPES = {
TOKEN_METHOD_SAFE_TRANSFER_FROM: 'safetransferfrom',
TOKEN_METHOD_TRANSFER: 'transfer',
TOKEN_METHOD_TRANSFER_FROM: 'transferfrom',
+ TOKEN_METHOD_SET_APPROVAL_FOR_ALL: 'setapprovalforall',
};
/**
diff --git a/shared/modules/transaction.utils.js b/shared/modules/transaction.utils.js
index 08adb46e3..1910ef2f3 100644
--- a/shared/modules/transaction.utils.js
+++ b/shared/modules/transaction.utils.js
@@ -8,7 +8,7 @@ import { readAddressAsContract } from './contract-utils';
import { isEqualCaseInsensitive } from './string-utils';
/**
- * @typedef { 'transfer' | 'approve' | 'transferfrom' | 'contractInteraction'| 'simpleSend' } InferrableTransactionTypes
+ * @typedef { 'transfer' | 'approve' | 'setapprovalforall' | 'transferfrom' | 'contractInteraction'| 'simpleSend' } InferrableTransactionTypes
*/
/**
@@ -150,6 +150,7 @@ export async function determineTransactionType(txParams, query) {
const tokenMethodName = [
TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
+ TRANSACTION_TYPES.TOKEN_METHOD_SET_APPROVAL_FOR_ALL,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM,
@@ -181,6 +182,7 @@ export async function determineTransactionType(txParams, query) {
const INFERRABLE_TRANSACTION_TYPES = [
TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
+ TRANSACTION_TYPES.TOKEN_METHOD_SET_APPROVAL_FOR_ALL,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
TRANSACTION_TYPES.CONTRACT_INTERACTION,
@@ -220,6 +222,7 @@ export async function determineTransactionAssetType(
// method to get the asset type.
const isTokenMethod = [
TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
+ TRANSACTION_TYPES.TOKEN_METHOD_SET_APPROVAL_FOR_ALL,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
].find((methodName) => methodName === inferrableType);
diff --git a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
index 2d5fb8193..e31e81bce 100644
--- a/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
+++ b/ui/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
@@ -53,7 +53,8 @@ const ConfirmPageContainerSummary = (props) => {
contractAddress =
transactionType === TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER ||
transactionType === TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM ||
- transactionType === TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM
+ transactionType === TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM ||
+ transactionType === TRANSACTION_TYPES.TOKEN_METHOD_SET_APPROVAL_FOR_ALL
? tokenAddress
: toAddress;
}
diff --git a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js
index 331885364..f65c82f63 100644
--- a/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js
+++ b/ui/components/app/transaction-list-item-details/transaction-list-item-details.component.js
@@ -251,7 +251,10 @@ export default class TransactionListItemDetails extends PureComponent {
param.name === '_value');
return valueData && valueData.value;
diff --git a/ui/helpers/utils/transactions.util.js b/ui/helpers/utils/transactions.util.js
index d1380d8c0..07d37341d 100644
--- a/ui/helpers/utils/transactions.util.js
+++ b/ui/helpers/utils/transactions.util.js
@@ -116,6 +116,7 @@ export function isTokenMethodAction(type) {
return [
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
+ TRANSACTION_TYPES.TOKEN_METHOD_SET_APPROVAL_FOR_ALL,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM,
].includes(type);
@@ -217,6 +218,9 @@ export function getTransactionTypeTitle(t, type, nativeCurrency = 'ETH') {
case TRANSACTION_TYPES.TOKEN_METHOD_APPROVE: {
return t('approve');
}
+ case TRANSACTION_TYPES.TOKEN_METHOD_SET_APPROVAL_FOR_ALL: {
+ return t('setApprovalForAll');
+ }
case TRANSACTION_TYPES.SIMPLE_SEND: {
return t('sendingNativeAsset', [nativeCurrency]);
}
diff --git a/ui/hooks/useTransactionDisplayData.js b/ui/hooks/useTransactionDisplayData.js
index 184374496..9c6a13768 100644
--- a/ui/hooks/useTransactionDisplayData.js
+++ b/ui/hooks/useTransactionDisplayData.js
@@ -222,6 +222,12 @@ export function useTransactionDisplayData(transactionGroup) {
title = t('approveSpendLimit', [token?.symbol || t('token')]);
subtitle = origin;
subtitleContainsOrigin = true;
+ } else if (type === TRANSACTION_TYPES.TOKEN_METHOD_SET_APPROVAL_FOR_ALL) {
+ category = TRANSACTION_GROUP_CATEGORIES.APPROVAL;
+ prefix = '';
+ title = t('setApprovalForAllTitle', [token?.symbol || t('token')]);
+ subtitle = origin;
+ subtitleContainsOrigin = true;
} else if (type === TRANSACTION_TYPES.CONTRACT_INTERACTION) {
category = TRANSACTION_GROUP_CATEGORIES.INTERACTION;
const transactionTypeTitle = getTransactionTypeTitle(t, type);
diff --git a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
index 7a6d32cce..cb831bbd3 100644
--- a/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
+++ b/ui/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js
@@ -71,6 +71,8 @@ export default class ConfirmApproveContent extends Component {
assetName: PropTypes.string,
tokenId: PropTypes.string,
assetStandard: PropTypes.string,
+ isSetApproveForAll: PropTypes.bool,
+ setApproveForAllArg: PropTypes.bool,
};
state = {
@@ -184,7 +186,7 @@ export default class ConfirmApproveContent extends Component {
renderERC721OrERC1155PermissionContent() {
const { t } = this.context;
- const { origin, toAddress, isContract } = this.props;
+ const { origin, toAddress, isContract, isSetApproveForAll } = this.props;
const titleTokenDescription = this.getTitleTokenDescription();
@@ -201,7 +203,9 @@ export default class ConfirmApproveContent extends Component {
{t('approvedAsset')}:
- {titleTokenDescription}
+ {isSetApproveForAll
+ ? `${t('allOfYour', [titleTokenDescription])} `
+ : titleTokenDescription}
@@ -299,12 +303,19 @@ export default class ConfirmApproveContent extends Component {
renderDataContent() {
const { t } = this.context;
- const { data } = this.props;
+ const { data, isSetApproveForAll, setApproveForAllArg } = this.props;
return (
- {t('functionApprove')}
+ {isSetApproveForAll
+ ? t('functionSetApprovalForAll')
+ : t('functionApprove')}
+ {isSetApproveForAll && setApproveForAllArg !== undefined ? (
+
+ {t('parameters')}: {setApproveForAllArg}
+
+ ) : null}
{data}
@@ -509,6 +520,41 @@ export default class ConfirmApproveContent extends Component {
return titleTokenDescription;
}
+ renderTitle() {
+ const { t } = this.context;
+ const { isSetApproveForAll, setApproveForAllArg } = this.props;
+ const titleTokenDescription = this.getTitleTokenDescription();
+
+ let title;
+
+ if (isSetApproveForAll) {
+ title = t('approveAllTokensTitle', [titleTokenDescription]);
+ if (setApproveForAllArg === false) {
+ title = t('revokeAllTokensTitle', [titleTokenDescription]);
+ }
+ }
+ return title || t('allowSpendToken', [titleTokenDescription]);
+ }
+
+ renderDescription() {
+ const { t } = this.context;
+ const { isContract, isSetApproveForAll, setApproveForAllArg } = this.props;
+ const grantee = isContract
+ ? t('contract').toLowerCase()
+ : t('account').toLowerCase();
+
+ let description = t('trustSiteApprovePermission', [grantee]);
+
+ if (isSetApproveForAll && setApproveForAllArg === false) {
+ description = t('revokeApproveForAllDescription', [
+ grantee,
+ this.getTitleTokenDescription(),
+ ]);
+ }
+
+ return description;
+ }
+
render() {
const { t } = this.context;
const {
@@ -534,8 +580,6 @@ export default class ConfirmApproveContent extends Component {
} = this.props;
const { showFullTxDetails } = this.state;
- const titleTokenDescription = this.getTitleTokenDescription();
-
return (
- {t('allowSpendToken', [titleTokenDescription])}
+ {this.renderTitle()}
- {t('trustSiteApprovePermission', [
- isContract
- ? t('contract').toLowerCase()
- : t('account').toLowerCase(),
- ])}
+ {this.renderDescription()}
diff --git a/ui/pages/confirm-approve/confirm-approve.js b/ui/pages/confirm-approve/confirm-approve.js
index 6dca97d1b..c1b35c24d 100644
--- a/ui/pages/confirm-approve/confirm-approve.js
+++ b/ui/pages/confirm-approve/confirm-approve.js
@@ -8,7 +8,10 @@ import {
updateCustomNonce,
getNextNonce,
} from '../../store/actions';
-import { calcTokenAmount } from '../../helpers/utils/token-util';
+import {
+ calcTokenAmount,
+ getTokenApprovedParam,
+} from '../../helpers/utils/token-util';
import { readAddressAsContract } from '../../../shared/modules/contract-utils';
import { GasFeeContextProvider } from '../../contexts/gasFee';
import { TransactionModalContextProvider } from '../../contexts/transaction-modal';
@@ -34,6 +37,7 @@ import EditGasFeePopover from '../../components/app/edit-gas-fee-popover';
import EditGasPopover from '../../components/app/edit-gas-popover/edit-gas-popover.component';
import Loading from '../../components/ui/loading-screen';
import { ERC20, ERC1155, ERC721 } from '../../helpers/constants/common';
+import { parseStandardTokenTransactionData } from '../../../shared/modules/transaction.utils';
import { getCustomTxParamsData } from './confirm-approve.util';
import ConfirmApproveContent from './confirm-approve-content';
@@ -57,6 +61,7 @@ export default function ConfirmApprove({
ethTransactionTotal,
fiatTransactionTotal,
hexTransactionTotal,
+ isSetApproveForAll,
}) {
const dispatch = useDispatch();
const { txParams: { data: transactionData } = {} } = transaction;
@@ -150,6 +155,11 @@ export default function ConfirmApprove({
})
: null;
+ const parsedTransactionData = parseStandardTokenTransactionData(
+ transactionData,
+ );
+ const setApproveForAllArg = getTokenApprovedParam(parsedTransactionData);
+
return tokenSymbol === undefined && assetName === undefined ? (
) : (
@@ -162,6 +172,8 @@ export default function ConfirmApprove({
contentComponent={
;
}
+ case TRANSACTION_TYPES.TOKEN_METHOD_SET_APPROVAL_FOR_ALL: {
+ const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_SET_APPROVAL_FOR_ALL_PATH}`;
+ return ;
+ }
case TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM: {
const pathname = `${CONFIRM_TRANSACTION_ROUTE}/${id}${CONFIRM_TRANSFER_FROM_PATH}`;
return ;
diff --git a/ui/pages/confirm-transaction/confirm-token-transaction-switch.js b/ui/pages/confirm-transaction/confirm-token-transaction-switch.js
index 036f2b149..43b554595 100644
--- a/ui/pages/confirm-transaction/confirm-token-transaction-switch.js
+++ b/ui/pages/confirm-transaction/confirm-token-transaction-switch.js
@@ -6,6 +6,7 @@ import {
CONFIRM_APPROVE_PATH,
CONFIRM_SAFE_TRANSFER_FROM_PATH,
CONFIRM_SEND_TOKEN_PATH,
+ CONFIRM_SET_APPROVAL_FOR_ALL_PATH,
CONFIRM_TRANSACTION_ROUTE,
CONFIRM_TRANSFER_FROM_PATH,
} from '../../helpers/constants/routes';
@@ -66,6 +67,30 @@ export default function ConfirmTokenTransactionSwitch({ transaction }) {
/>
)}
/>
+ (
+
+ )}
+ />