Repository: MetaMask/metamask-mobile
Pull request #13666: feat: support DAI while it's being deprecated
Author: login: digiwand
Labels: name: team-confirmations
description: Push issues to confirmations team
name: release-7.43.0
description: Issue or pull request that will be included in release 7.43.0
Created: 2025-02-21T14:43:14Z | Merged: 2025-03-07T17:28:41Z

<!--
Please submit this PR as a draft initially.
Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once.
-->

## **Description**

Supports DAI while it's being deprecated (see DAI upgrade to USDS).

This PR includes: 
https://github.com/MetaMask/metamask-mobile/issues/13662 (Empty value display shown)
https://github.com/MetaMask/metamask-mobile/pull/13663 (Revoke)

I'll need to resolve merge conflicts when the above two are merged. 

## **Related issues**

Fixes: https://github.com/MetaMask/metamask-mobile/issues/13336

## **Manual testing steps**

1. Go to https://develop.d3bkcslj57l47p.amplifyapp.com/
2. Trigger Permit DAI

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. -->

### **Before**

<!-- [screenshots/recordings] -->

### **After**

<img width="320" src="https://github.com/user-attachments/assets/06c7d0c2-fb18-4bc5-acd8-96c761bbfc26">
<img width="320" src="https://github.com/user-attachments/assets/a5d85bdd-fd3e-4c8c-87a8-f4015000fde0">

## **Pre-merge author checklist**

- [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [ ] I've completed the PR template to the best of my ability
- [ ] I’ve included tests if applicable
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable
- [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.



Diff:
diff --git a/app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.styles.ts b/app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.styles.ts
index a5d937a948c..c223002ca32 100644
--- a/app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.styles.ts
+++ b/app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.styles.ts
@@ -26,7 +26,7 @@ const styleSheet = (params: {
 }) => {
   const {
     theme: { colors },
-    vars: { isDisabled, isPressed }
+    vars: { isPressed }
   } = params;
 
   return StyleSheet.create({
@@ -38,7 +38,6 @@ const styleSheet = (params: {
       paddingHorizontal: 8,
       paddingVertical: 4,
       borderRadius: 99,
-      opacity: isDisabled ? 0.5 : 1,
       ...(isPressed && {
         backgroundColor: colors.background.alternativePressed,
       }),
diff --git a/app/component-library/components-temp/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap b/app/component-library/components-temp/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap
index c3e3b781a54..82d91e3dbe8 100644
--- a/app/component-library/components-temp/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap
+++ b/app/component-library/components-temp/Buttons/ButtonPill/__snapshots__/ButtonPill.test.tsx.snap
@@ -15,7 +15,6 @@ exports[`ButtonPill should render correctly 1`] = `
       "borderRadius": 99,
       "color": "#141618",
       "justifyContent": "center",
-      "opacity": 1,
       "paddingHorizontal": 8,
       "paddingVertical": 4,
     }
diff --git a/app/components/Views/confirmations/Confirm/Confirm.test.tsx b/app/components/Views/confirmations/Confirm/Confirm.test.tsx
index bf12b63f2ee..13ef0137458 100644
--- a/app/components/Views/confirmations/Confirm/Confirm.test.tsx
+++ b/app/components/Views/confirmations/Confirm/Confirm.test.tsx
@@ -22,6 +22,7 @@ jest.mock('@react-navigation/native', () => ({
     goBack: jest.fn(),
     navigate: jest.fn(),
     removeListener: jest.fn(),
+    setOptions: jest.fn(),
   }),
 }));
 
@@ -75,17 +76,11 @@ jest.mock('react-native-gzip', () => ({
   deflate: (str: string) => str,
 }));
 
-jest.mock('@react-navigation/native', () => ({
-  ...jest.requireActual('@react-navigation/native'),
-  useNavigation: () => ({
-    navigate: jest.fn(),
-    addListener: jest.fn(),
-    dispatch: jest.fn(),
-    setOptions: jest.fn(),
-  }),
-}));
-
 describe('Confirm', () => {
+  afterEach(() => {
+    jest.restoreAllMocks();
+  });
+
   it('renders modal confirmation', async () => {
     const { getByTestId } = renderWithProvider(<Confirm />, {
       state: typedSignV1ConfirmationState,
@@ -159,7 +154,9 @@ describe('Confirm', () => {
       },
     });
 
-    await act(async () => undefined);
+    await act(async () => {
+      await Promise.resolve();
+    });
 
     expect(getByText('Signature request')).toBeDefined();
     expect(getByText('This is a deceptive request')).toBeDefined();
diff --git a/app/components/Views/confirmations/components/Confirm/DataTree/DataField.tsx b/app/components/Views/confirmations/components/Confirm/DataTree/DataField.tsx
index 3ab8ab77da5..c828fe59a06 100644
--- a/app/components/Views/confirmations/components/Confirm/DataTree/DataField.tsx
+++ b/app/components/Views/confirmations/components/Confirm/DataTree/DataField.tsx
@@ -125,6 +125,8 @@ const DataField = memo(
           tokenDecimals={tokenDecimals}
         />
       );
+    } else if (type === 'bool') {
+      fieldDisplay = <Text>{value ? 'true' : 'false'}</Text>;
     } else {
       fieldDisplay = <Text>{value}</Text>;
     }
diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.test.tsx
index 2a1011dd118..52e311bffe6 100644
--- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.test.tsx
+++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/Simulation.test.tsx
@@ -28,7 +28,7 @@ const stateChangesApprove = [
     changeType: DecodingDataChangeType.Approve,
     address: '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad',
     amount: '12345',
-    contractAddress: '0x6b175474e89094c44da98b954eedeac495271d0f',
+    contractAddress: '0x514910771af9ca656af840dff83e8264ecf986ca',
   },
 ];
 
diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx
index b0b88c70c52..379492c0f1d 100644
--- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx
+++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx
@@ -22,7 +22,7 @@ const stateChangesApprove = [
     changeType: DecodingDataChangeType.Approve,
     address: '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad',
     amount: '12345',
-    contractAddress: '0x6b175474e89094c44da98b954eedeac495271d0f',
+    contractAddress: '0x514910771af9ca656af840dff83e8264ecf986ca',
   },
 ];
 
@@ -90,6 +90,26 @@ const stateChangesNftBidding: DecodingDataStateChanges = [
   },
 ];
 
+const stateChangesApproveDAI: DecodingDataStateChanges = [
+  {
+    assetType: 'ERC20',
+    changeType: DecodingDataChangeType.Approve,
+    address: '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad',
+    amount: '1461501637330902918203684832716283019655932542975',
+    contractAddress: '0x6b175474e89094c44da98b954eedeac495271d0f',
+  },
+];
+
+const stateChangesRevokeDAI: DecodingDataStateChanges = [
+  {
+    assetType: 'ERC20',
+    changeType: DecodingDataChangeType.Revoke,
+    address: '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad',
+    amount: '0',
+    contractAddress: '0x6b175474e89094c44da98b954eedeac495271d0f',
+  },
+];
+
 const mockState = (
   mockStateChanges: DecodingDataStateChanges,
   stubDecodingLoading: boolean = false,
@@ -122,15 +142,15 @@ describe('DecodedSimulation', () => {
     await waitFor(() => expect(getByText('12,345')).toBeDefined());
   });
 
-  it('renders for ERC20 revoke', async () => {
-    const { getByText } = renderWithProvider(<TypedSignDecoded />, {
+  it('renders for ERC20 revoke', () => {
+    const { queryByText } = renderWithProvider(<TypedSignDecoded />, {
       state: mockState(stateChangesRevoke),
     });
 
-    expect(await getByText('Estimated changes')).toBeDefined();
-    expect(await getByText('Revoke')).toBeDefined();
+    expect(queryByText('Estimated changes')).toBeDefined();
+    expect(queryByText('Revoke')).toBeDefined();
 
-    await waitFor(() => expect(getByText('12,345')).toBeDefined());
+    expect(queryByText('Unlimited')).toBeNull();
   });
 
   it('renders "Unlimited" for large values', async () => {
@@ -147,6 +167,24 @@ describe('DecodedSimulation', () => {
     await waitFor(() => expect(getByText('Unlimited')).toBeDefined());
   });
 
+  it('renders "Unlimited" for backwards compatibility approve DAI', () => {
+    const { queryByText } = renderWithProvider(<TypedSignDecoded />, {
+      state: mockState(stateChangesApproveDAI),
+    });
+
+    expect(queryByText('Spending cap')).toBeTruthy();
+    expect(queryByText('Unlimited')).toBeNull();
+  });
+
+  it('renders backwards compatibility revoke DAI', () => {
+    const { queryByText } = renderWithProvider(<TypedSignDecoded />, {
+      state: mockState(stateChangesRevokeDAI),
+    });
+
+    expect(queryByText('Revoke')).toBeTruthy();
+    expect(queryByText('Unlimited')).toBeNull();
+  });
+
   it('renders for ERC712 token', async () => {
     const { getByText } = renderWithProvider(<TypedSignDecoded />, {
       state: mockState(stateChangesNftListing),
diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx
index 42091bb07dc..4399dd66b79 100644
--- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx
+++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.tsx
@@ -125,6 +125,7 @@ const StateChangeRow = ({
         <SimulationValueDisplay
           modalHeaderText={changeType}
           tokenContract={contractAddress}
+          allowed={amount}
           value={amount}
           chainId={chainId}
           tokenId={tokenID}
diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx
index f89591f22d1..21f4ba43a82 100644
--- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx
+++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.test.tsx
@@ -1,8 +1,14 @@
 import React from 'react';
 import { waitFor } from '@testing-library/react-native';
 
+import { backgroundState } from '../../../../../../../../../util/test/initial-root-state';
 import renderWithProvider from '../../../../../../../../../util/test/renderWithProvider';
-import { typedSignV4ConfirmationState, typedSignV4NFTConfirmationState } from '../../../../../../../../../util/test/confirm-data-helpers';
+import {
+  SignTypedDataMockType,
+  generateStateSignTypedData,
+  typedSignV4ConfirmationState,
+  typedSignV4NFTConfirmationState,
+} from '../../../../../../../../../util/test/confirm-data-helpers';
 import PermitSimulation from './TypedSignPermit';
 
 jest.mock('../../../../../../../../../core/Engine', () => ({
@@ -13,8 +19,16 @@ jest.mock('../../../../../../../../../core/Engine', () => ({
   },
 }));
 
+jest.mock('react-native/Libraries/Linking/Linking', () => ({
+  addEventListener: jest.fn(),
+  removeEventListener: jest.fn(),
+  openURL: jest.fn(),
+  canOpenURL: jest.fn(),
+  getInitialURL: jest.fn(),
+}));
+
 describe('PermitSimulation', () => {
-  it('should render correctly for Permit', async () => {
+  it('renders for Permit', async () => {
     const { getByText } = renderWithProvider(<PermitSimulation />, {
       state: typedSignV4ConfirmationState,
     });
@@ -31,12 +45,57 @@ describe('PermitSimulation', () => {
     await waitFor(() => expect(getByText('3,000')).toBeTruthy());
   });
 
-  it('should render correctly for Permit NFTs', async () => {
+  it('renders null when no message data is found', async () => {
+    const { queryByText } = renderWithProvider(<PermitSimulation />, {
+      state: {
+        engine: {
+          backgroundState: {
+            ...backgroundState,
+          },
+        },
+      },
+    });
+    expect(queryByText('Estimated changes')).toBeNull();
+  });
+
+  it('renders for DAI Revoke', async () => {
     const { getByText } = renderWithProvider(<PermitSimulation />, {
-      state: typedSignV4NFTConfirmationState,
+      state: generateStateSignTypedData(SignTypedDataMockType.DAI),
     });
 
     expect(getByText('Estimated changes')).toBeTruthy();
+    expect(
+      getByText(
+        "You're removing someone's permission to spend tokens from your account.",
+      ),
+    ).toBeTruthy();
+    expect(getByText('Revoke')).toBeTruthy();
+    expect(getByText('0x6B175...71d0F')).toBeTruthy();
+  });
+
+  it('renders for Permit Batch', async () => {
+    const { getByText } = renderWithProvider(<PermitSimulation />, {
+      state: generateStateSignTypedData(SignTypedDataMockType.BATCH),
+    });
+
+    expect(
+      getByText(
+        "You're giving the spender permission to spend this many tokens from your account.",
+      ),
+    ).toBeTruthy();
+    expect(getByText('Spending cap')).toBeTruthy();
+    expect(getByText('0xA0b86...6eB48')).toBeTruthy();
+    expect(getByText('0xb0B86...6EB48')).toBeTruthy();
+
+    await waitFor(() => expect(getByText('1,461,501,637,3...')).toBeTruthy());
+    await waitFor(() => expect(getByText('250')).toBeTruthy());
+  });
+
+  it('renders for Permit NFTs', async () => {
+    const { getByText } = renderWithProvider(<PermitSimulation />, {
+      state: typedSignV4NFTConfirmationState,
+    });
+
     expect(
       getByText(
         "You're giving the spender permission to spend this many tokens from your account.",
diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.tsx
index 42d0a0e841e..2e66193662d 100644
--- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.tsx
+++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.tsx
@@ -8,7 +8,7 @@ import Engine from '../../../../../../../../../core/Engine';
 import { safeToChecksumAddress } from '../../../../../../../../../util/address';
 import { PrimaryType } from '../../../../../../constants/signatures';
 import { useSignatureRequest } from '../../../../../../hooks/useSignatureRequest';
-import { parseTypedDataMessage } from '../../../../../../utils/signature';
+import { isPermitDaiRevoke, parseTypedDataMessage } from '../../../../../../utils/signature';
 import InfoRow from '../../../../../UI/InfoRow';
 import InfoSection from '../../../../../UI/InfoRow/InfoSection';
 import PermitSimulationValueDisplay from '../components/ValueDisplay';
@@ -22,10 +22,17 @@ const styleSheet = () =>
     },
   });
 
+interface TokenDetails {
+  allowed?: string;
+  amount: string;
+  token: string;
+  // ... other properties may exist
+}
+
 function extractTokenDetailsByPrimaryType(
   message: Record<string, unknown>,
   primaryType: PrimaryType,
-): object[] | unknown {
+): TokenDetails[] | unknown {
   let tokenDetails;
 
   switch (primaryType) {
@@ -65,7 +72,7 @@ const PermitSimulation = () => {
   const {
     domain: { verifyingContract },
     message,
-    message: { tokenId },
+    message: { allowed, tokenId, value },
     primaryType,
   } = parseTypedDataMessage(msgData as string);
 
@@ -73,9 +80,20 @@ const PermitSimulation = () => {
 
   const isNFT = tokenId !== undefined && tokenId !== '0';
 
-  const labelChangeType = isNFT
+  let labelChangeType = isNFT
     ? strings('confirm.simulation.label_change_type_permit_nft')
-    : strings('confirm.simulation.label_change_type_permit');
+      : strings('confirm.simulation.label_change_type_permit');
+
+  const isDaiRevoke = isPermitDaiRevoke(verifyingContract, allowed, value);
+  const isRevoke = isDaiRevoke || value === '0';
+
+  if (isRevoke) {
+    labelChangeType = strings('confirm.simulation.label_change_type_revoke');
+  }
+
+  const infoText = isRevoke
+    ? strings('confirm.simulation.info_revoke')
+    : strings('confirm.simulation.info_permit');
 
   return (
     <InfoSection>
@@ -83,7 +101,7 @@ const PermitSimulation = () => {
         label={strings('confirm.simulation.title')}
         tooltip={strings('confirm.simulation.tooltip')}
       >
-        {strings('confirm.simulation.info_permit')}
+        {infoText}
       </InfoRow>
 
       <InfoRow label={labelChangeType}>
@@ -91,20 +109,25 @@ const PermitSimulation = () => {
           <>
             {tokenDetails.map(
               (
-                { token, amount }: { token: string; amount: string },
+                { allowed: tokenDetailAllowed, token, amount },
                 i: number,
-              ) => (
-                <View style={styles.permitValues} key={`${token}-${i}`}>
-                  <PermitSimulationValueDisplay
-                    modalHeaderText={labelChangeType}
-                    networkClientId={networkClientId}
-                    primaryType={primaryType}
-                    tokenContract={safeToChecksumAddress(token)}
-                    value={amount}
-                    chainId={chainId}
-                  />
-                </View>
-              ),
+              ) => {
+                const tokenContract = safeToChecksumAddress(token);
+
+                return (
+                  <View style={styles.permitValues} key={`${token}-${i}`}>
+                    <PermitSimulationValueDisplay
+                      modalHeaderText={labelChangeType}
+                      networkClientId={networkClientId}
+                      primaryType={primaryType}
+                      tokenContract={tokenContract}
+                      value={amount}
+                      chainId={chainId}
+                      allowed={tokenDetailAllowed}
+                    />
+                  </View>
+                );
+              },
             )}
           </>
         ) : (
@@ -112,6 +135,7 @@ const PermitSimulation = () => {
             modalHeaderText={labelChangeType}
             networkClientId={networkClientId}
             tokenContract={verifyingContract}
+            allowed={message.allowed}
             value={message.value}
             tokenId={message.tokenId}
             chainId={chainId}
diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx
index 8a6731aaf98..50c24f50df7 100644
--- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx
+++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.test.tsx
@@ -17,6 +17,17 @@ const mockInitialState = {
   },
 };
 
+const mockErc20TokenDetails = {
+  details: {
+    symbol: 'TST',
+    decimals: '4',
+    balance: undefined,
+    standard: TokenStandard.ERC20,
+    decimalsNumber: 4,
+  },
+  isPending: false,
+};
+
 const mockTrackEvent = jest.fn();
 
 jest.mock('../../../../../../../../../hooks/useMetrics');
@@ -58,16 +69,7 @@ describe('SimulationValueDisplay', () => {
       useGetTokenStandardAndDetails as jest.MockedFn<
         typeof useGetTokenStandardAndDetails
       >
-    ).mockReturnValue({
-      details: {
-        symbol: 'TST',
-        decimals: '4',
-        balance: undefined,
-        standard: TokenStandard.ERC20,
-        decimalsNumber: 4,
-      },
-      isPending: false,
-    });
+    ).mockReturnValue(mockErc20TokenDetails);
 
     const { findByText } = renderWithProvider(
       <SimulationValueDisplay
@@ -101,6 +103,20 @@ describe('SimulationValueDisplay', () => {
     expect(await findByTestId('simulation-value-display-loader')).toBeDefined();
   });
 
+  it('renders null when tokenContract is not found', async () => {
+    const { queryByTestId } = renderWithProvider(
+      <SimulationValueDisplay
+        modalHeaderText={'Spending Cap'}
+        tokenContract={undefined}
+        value={'4321'}
+        chainId={'0x1'}
+      />,
+      { state: mockInitialState },
+    );
+
+    expect(queryByTestId('simulation-value-display-loader')).toBeNull();
+  });
+
   it('renders no value display if no value was loaded', () => {
     (useGetTokenStandardAndDetails as jest.MockedFn<typeof useGetTokenStandardAndDetails>).mockReturnValue({
       details: { decimalsNumber: undefined },
@@ -116,20 +132,12 @@ describe('SimulationValueDisplay', () => {
       />,
       { state: mockInitialState },
     );
-    expect(queryByTestId('simulation-value-display-loader')).not.toBeTruthy();
+    expect(queryByTestId('simulation-value-display-loader')).toBeNull();
   });
 
   it('renders "Unlimited" for large values when canDisplayValueAsUnlimited is true', async () => {
-    (useGetTokenStandardAndDetails as jest.MockedFn<typeof useGetTokenStandardAndDetails>).mockReturnValue({
-      details: {
-        symbol: 'TST',
-        decimals: '4',
-        balance: undefined,
-        standard: TokenStandard.ERC20,
-        decimalsNumber: 4,
-      },
-      isPending: false,
-    });
+    (useGetTokenStandardAndDetails as jest.MockedFn<typeof useGetTokenStandardAndDetails>)
+      .mockReturnValue(mockErc20TokenDetails);
 
     const { findByText } = renderWithProvider(
       <SimulationValueDisplay
@@ -145,6 +153,63 @@ describe('SimulationValueDisplay', () => {
     expect(await findByText('Unlimited')).toBeDefined();
   });
 
+  it('renders Dai Revoke', async () => {
+    (useGetTokenStandardAndDetails as jest.MockedFn<typeof useGetTokenStandardAndDetails>)
+      .mockReturnValue(mockErc20TokenDetails);
+
+    const { findByText } = renderWithProvider(
+      <SimulationValueDisplay
+        canDisplayValueAsUnlimited
+        modalHeaderText={'Revoke'}
+        tokenContract={'0x6b175474e89094c44da98b954eedeac495271d0f'}
+        value={'12345'}
+        chainId={'0x1'}
+        allowed={false}
+      />,
+      { state: mockInitialState },
+    );
+
+    expect(await findByText('0x6B175...71d0F')).toBeDefined();
+  });
+
+  it('renders Dai Revoke when value is 0', async () => {
+    (useGetTokenStandardAndDetails as jest.MockedFn<typeof useGetTokenStandardAndDetails>)
+      .mockReturnValue(mockErc20TokenDetails);
+
+    const { findByText } = renderWithProvider(
+      <SimulationValueDisplay
+        canDisplayValueAsUnlimited
+        modalHeaderText={'Revoke'}
+        tokenContract={'0x6b175474e89094c44da98b954eedeac495271d0f'}
+        value={'0'}
+        chainId={'0x1'}
+      />,
+      { state: mockInitialState },
+    );
+
+    expect(await findByText('0x6B175...71d0F')).toBeDefined();
+  });
+
+  it('renders Dai Approve', async () => {
+    (useGetTokenStandardAndDetails as jest.MockedFn<typeof useGetTokenStandardAndDetails>)
+      .mockReturnValue(mockErc20TokenDetails);
+
+    const { findByText } = renderWithProvider(
+      <SimulationValueDisplay
+        canDisplayValueAsUnlimited
+        modalHeaderText={'Spending cap'}
+        tokenContract={'0x6b175474e89094c44da98b954eedeac495271d0f'}
+        value={undefined}
+        chainId={'0x1'}
+        allowed
+      />,
+      { state: mockInitialState },
+    );
+
+    expect(await findByText('0x6B175...71d0F')).toBeDefined();
+    expect(await findByText('Unlimited')).toBeDefined();
+  });
+
   it('invokes method to track missing decimal information for ERC20 tokens only once', async () => {
     (
       useGetTokenStandardAndDetails as jest.MockedFn<
@@ -179,16 +244,7 @@ describe('SimulationValueDisplay', () => {
       useGetTokenStandardAndDetails as jest.MockedFn<
         typeof useGetTokenStandardAndDetails
       >
-    ).mockReturnValue({
-      details: {
-        symbol: 'TST',
-        decimals: '4',
-        balance: undefined,
-        standard: TokenStandard.ERC20,
-        decimalsNumber: 4,
-      },
-      isPending: false,
-    });
+    ).mockReturnValue(mockErc20TokenDetails);
 
     renderWithProvider(
       <SimulationValueDisplay
diff --git a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx
index 7a0f0aa3aa2..3afd83119d0 100644
--- a/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx
+++ b/app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/components/ValueDisplay/ValueDisplay.tsx
@@ -31,6 +31,7 @@ import { calcTokenAmount } from '../../../../../../../../../../util/transactions
 import { useGetTokenStandardAndDetails } from '../../../../../../../hooks/useGetTokenStandardAndDetails';
 import useTrackERC20WithoutDecimalInformation from '../../../../../../../hooks/useTrackERC20WithoutDecimalInformation';
 import { TOKEN_VALUE_UNLIMITED_THRESHOLD } from '../../../../../../../utils/confirm';
+import { isPermitDaiRevoke, isPermitDaiUnlimited } from '../../../../../../../utils/signature';
 import { TokenDetailsERC20 } from '../../../../../../../utils/token';
 import BottomModal from '../../../../../../UI/BottomModal';
 
@@ -59,6 +60,9 @@ interface SimulationValueDisplayParams {
 
   // Optional
 
+  /** Value for backwards compatibility DAI EIP-2612 support while it is being depreacted */
+  allowed?: boolean | number | string;
+
   /** Whether a large amount can be substituted by "Unlimited" */
   canDisplayValueAsUnlimited?: boolean;
 
@@ -88,6 +92,7 @@ const SimulationValueDisplay: React.FC<SimulationValueDisplayParams> = ({
   value,
   credit,
   debit,
+  allowed,
   canDisplayValueAsUnlimited = false,
 }) => {
   const [hasValueModalOpen, setHasValueModalOpen] = useState(false);
@@ -115,14 +120,29 @@ const SimulationValueDisplay: React.FC<SimulationValueDisplayParams> = ({
     tokenDetails as TokenDetailsERC20,
   );
 
+  /** Temporary error capturing as we are building out Permit Simulations */
+  if (!tokenContract) {
+    Logger.error(
+      new Error(
+        `SimulationValueDisplay: Token contract address is missing where primaryType === ${primaryType}`,
+      ),
+    );
+    return null;
+  }
+
   const isNFT = tokenId !== undefined && tokenId !== '0';
+  const isDaiUnlimited = isPermitDaiUnlimited(tokenContract, allowed);
+  const isDaiRevoke = isPermitDaiRevoke(tokenContract, allowed, value);
+  const isRevoke = isDaiRevoke || modalHeaderText === strings('confirm.title.permit_revoke');
 
   const tokenAmount =
     isNumberValue(value) && !tokenId
       ? calcTokenAmount(value as number | string, tokenDecimals)
       : null;
+
   const isValidTokenAmount =
     !isNFT &&
+    !isRevoke &&
     tokenAmount !== null &&
     tokenAmount !== undefined &&
     tokenAmount instanceof BigNumber;
@@ -135,27 +155,18 @@ const SimulationValueDisplay: React.FC<SimulationValueDisplayParams> = ({
   const tokenValue = isValidTokenAmount
     ? formatAmount('en-US', tokenAmount)
     : null;
+
   const tokenValueMaxPrecision = isValidTokenAmount
     ? formatAmountMaxPrecision('en-US', tokenAmount)
     : null;
 
-  const shouldShowUnlimitedValue =
-    canDisplayValueAsUnlimited &&
-    Number(value) > TOKEN_VALUE_UNLIMITED_THRESHOLD;
-
-  /** Temporary error capturing as we are building out Permit Simulations */
-  if (!tokenContract) {
-    Logger.error(
-      new Error(
-        `SimulationValueDisplay: Token contract address is missing where primaryType === ${primaryType}`,
-      ),
-    );
-    return null;
-  }
+  const showUnlimitedValue = isDaiUnlimited ||
+    (canDisplayValueAsUnlimited &&
+    Number(value) > TOKEN_VALUE_UNLIMITED_THRESHOLD);
 
   // Avoid empty button pill container
   const showValueButtonPill = Boolean(isPendingTokenDetails
-    || shouldShowUnlimitedValue
+    || showUnlimitedValue
     || (tokenValue !== null || tokenId));
 
   function handlePressTokenValue() {
@@ -169,7 +180,7 @@ const SimulationValueDisplay: React.FC<SimulationValueDisplayParams> = ({
             {showValueButtonPill &&
               <AnimatedPulse isPulsing={isPendingTokenDetails} testID="simulation-value-display-loader">
                 <ButtonPill
-                  isDisabled={isNFT}
+                  isDisabled={isNFT || tokenValueMaxPrecision === null}
                   onPress={handlePressTokenValue}
                   onPressIn={handlePressTokenValue}
                   onPressOut={handlePressTokenValue}
@@ -181,7 +192,7 @@ const SimulationValueDisplay: React.FC<SimulationValueDisplayParams> = ({
                   <Text>
                     {credit && '+ '}
                     {debit && '- '}
-                    {shouldShowUnlimitedValue
+                    {showUnlimitedValue
                       ? strings('confirm.unlimited')
                       : tokenValue !== null &&
                         shortenString(tokenValue || '', {
diff --git a/app/components/Views/confirmations/components/Confirm/Title/Title.tsx b/app/components/Views/confirmations/components/Confirm/Title/Title.tsx
index 21f10c3a168..136d606c8bf 100644
--- a/app/components/Views/confirmations/components/Confirm/Title/Title.tsx
+++ b/app/components/Views/confirmations/components/Confirm/Title/Title.tsx
@@ -9,7 +9,7 @@ import { useStyles } from '../../../../../../component-library/hooks';
 import Text from '../../../../../../component-library/components/Texts/Text';
 import useApprovalRequest from '../../../hooks/useApprovalRequest';
 import { useSignatureRequest } from '../../../hooks/useSignatureRequest';
-import { isSIWESignatureRequest , isRecognizedPermit, parseTypedDataMessageFromSignatureRequest } from '../../../utils/signature';
+import { isSIWESignatureRequest , isRecognizedPermit, parseTypedDataMessageFromSignatureRequest, isPermitDaiRevoke } from '../../../utils/signature';
 import { useStandaloneConfirmation } from '../../../hooks/useStandaloneConfirmation';
 import styleSheet from './Title.styles';
 
@@ -33,20 +33,33 @@ const getTitleAndSubTitle = (approvalRequest?: ApprovalRequest<{ data: string }>
       const isPermit = isRecognizedPermit(signatureRequest);
 
       if (isPermit) {
-        const { message } = parseTypedDataMessageFromSignatureRequest(signatureRequest);
-        const isERC721Permit = message?.tokenId !== undefined;
+        const parsedMessage = parseTypedDataMessageFromSignatureRequest(signatureRequest) ?? {};
+        const { allowed, tokenId, value } = parsedMessage?.message ?? {};
+        const { verifyingContract } = parsedMessage?.domain ?? {};
 
+        const isERC721Permit = tokenId !== undefined;
         if (isERC721Permit) {
           return {
             title: strings('confirm.title.permit_NFTs'),
             subTitle: strings('confirm.sub_title.permit_NFTs'),
           };
         }
+
+        const isDaiRevoke = isPermitDaiRevoke(verifyingContract, allowed, value);
+        const isRevoke = isDaiRevoke || value === '0';
+        
+        if (isRevoke) {
+          return {
+            title: strings('confirm.title.permit_revoke'),
+          };
+        }
+
         return {
           title: strings('confirm.title.permit'),
           subTitle: strings('confirm.sub_title.permit'),
         };
       }
+
       return {
         title: strings('confirm.title.signature'),
         subTitle: strings('confirm.sub_title.signature'),
@@ -72,7 +85,7 @@ const Title = () => {
   return (
     <View style={styles.titleContainer}>
       <Text style={styles.title}>{title}</Text>
-      <Text style={styles.subTitle}>{subTitle}</Text>
+      {subTitle && <Text style={styles.subTitle}>{subTitle}</Text>}
     </View>
   );
 };
diff --git a/app/components/Views/confirmations/constants/tokens.ts b/app/components/Views/confirmations/constants/tokens.ts
new file mode 100644
index 00000000000..88f59cd469e
--- /dev/null
+++ b/app/components/Views/confirmations/constants/tokens.ts
@@ -0,0 +1,3 @@
+export const TOKEN_ADDRESS = {
+  DAI: '0x6b175474e89094c44da98b954eedeac495271d0f',
+};
diff --git a/app/components/Views/confirmations/utils/signature.ts b/app/components/Views/confirmations/utils/signature.ts
index d2c2b8f800b..35880f8f1e3 100644
--- a/app/components/Views/confirmations/utils/signature.ts
+++ b/app/components/Views/confirmations/utils/signature.ts
@@ -12,6 +12,8 @@ import {
   PrimaryType,
 } from '../constants/signatures';
 import { sanitizeMessage } from '../../../../util/string';
+import { TOKEN_ADDRESS } from '../constants/tokens';
+import BigNumber from 'bignumber.js';
 
 /**
  * The contents of this file have been taken verbatim from
@@ -62,6 +64,29 @@ interface TypedSignatureRequest {
   type: SignatureRequestType.TypedSign;
 }
 
+/**
+ * Support backwards compatibility DAI while it's still being deprecated. See EIP-2612 for more info.
+ */
+export const isPermitDaiUnlimited = (tokenAddress: string, allowed?: number|string|boolean) => {
+  if (!tokenAddress) return false;
+
+  return tokenAddress.toLowerCase() === TOKEN_ADDRESS.DAI.toLowerCase()
+    && Number(allowed) > 0;
+};
+
+export const isPermitDaiRevoke = (tokenAddress: string, allowed?: number|string|boolean, value?: number|string|BigNumber) => {
+  if (!tokenAddress) return false;
+
+  return tokenAddress.toLowerCase() === TOKEN_ADDRESS.DAI.toLowerCase()
+    && (
+      allowed === 0
+      || allowed === false
+      || allowed === 'false'
+      || value === '0'
+      || (value instanceof BigNumber && value.eq(0))
+    );
+};
+
 /**
  * Returns true if the request is Typed Sign V3 or V4 request
  *
diff --git a/app/util/test/confirm-data-helpers.ts b/app/util/test/confirm-data-helpers.ts
index 951580e225a..ae64efb5d8e 100644
--- a/app/util/test/confirm-data-helpers.ts
+++ b/app/util/test/confirm-data-helpers.ts
@@ -15,6 +15,7 @@ import { Hex } from '@metamask/utils';
 
 import { backgroundState } from './initial-root-state';
 import { merge } from 'lodash';
+
 export const confirmationRedesignRemoteFlagsState = {
   remoteFeatureFlags: {
     confirmation_redesign: {
@@ -24,6 +25,13 @@ export const confirmationRedesignRemoteFlagsState = {
   },
 };
 
+const mockTypeDefEIP712Domain = [
+  { name: 'name', type: 'string' },
+  { name: 'version', type: 'string' },
+  { name: 'chainId', type: 'uint256' },
+  { name: 'verifyingContract', type: 'address' }
+];
+
 export const personalSignSignatureRequest = {
   chainId: '0x1',
   type: SignatureRequestType.PersonalSign,
@@ -696,4 +704,135 @@ export const stakingWithdrawalConfirmationState = merge(
       } as unknown as TransactionControllerState,
     },
   },
-);
\ No newline at end of file
+);
+
+export enum SignTypedDataMockType {
+  BATCH = 'BATCH',
+  DAI = 'DAI',
+}
+
+const SIGN_TYPE_DATA: Record<SignTypedDataMockType, string> = {
+  [SignTypedDataMockType.BATCH]: JSON.stringify({
+    types: {
+      EIP712Domain: mockTypeDefEIP712Domain,
+      PermitBatch: [
+        { name: 'details', type: 'PermitDetails[]' },
+        { name: 'spender', type: 'address' },
+        { name: 'sigDeadline', type: 'uint256' }
+      ],
+      PermitDetails: [
+        { name: 'token', type: 'address' },
+        { name: 'amount', type: 'uint160' },
+        { name: 'expiration', type: 'uint48' },
+        { name: 'nonce', type: 'uint48' }
+      ],
+    },
+    domain: {
+      name: 'Permit2',
+      chainId: '1',
+      version: '1',
+      verifyingContract: '0x000000000022d473030f116ddee9f6b43ac78ba3'
+    },
+    primaryType: 'PermitBatch',
+    message: {
+      details: [
+        {
+          token: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
+          amount: '1461501637330902918203684832716283019655932542975',
+          expiration: '1722887542',
+          nonce: '5'
+        },
+        {
+          token: '0xb0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
+          amount: '250',
+          expiration: '1722887642',
+          nonce: '6'
+        }
+      ],
+      spender: '0x3fc91a3afd70395cd496c647d5a6cc9d4b2b7fad',
+      sigDeadline: '1720297342'
+    }
+  }),
+  [SignTypedDataMockType.DAI]: JSON.stringify({
+    domain: {
+      name: 'Dai Stablecoin',
+      version: '1',
+      chainId: 1,
+      verifyingContract: '0x6B175474E89094C44Da98b954EedeAC495271d0F'
+    },
+    types: {
+      EIP712Domain: mockTypeDefEIP712Domain,
+      Permit: [
+        { name: 'holder', type: 'address' },
+        { name: 'spender', type: 'address' },
+        { name: 'nonce', type: 'uint256' },
+        { name: 'expiry', type: 'uint256' },
+        { name: 'allowed', type: 'bool' }
+      ]
+    },
+    primaryType: 'Permit',
+    message: {
+      spender: '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4',
+      tokenId: '3606393',
+      nonce: 0,
+      expiry: 0,
+      allowed: false
+    },
+  }),
+};
+
+export function generateStateSignTypedData(mockType: SignTypedDataMockType) {
+  const mockSignatureRequest = {
+    id: 'c5067710-87cf-11ef-916c-71f266571322',
+    chainId: '0x1' as Hex,
+    type: SignatureRequestType.TypedSign,
+    messageParams: {
+      data: SIGN_TYPE_DATA[mockType],
+      from: '0x935e73edb9ff52e23bac7f7e043a1ecd06d05477',
+      version: 'V4',
+      requestId: 14,
+      signatureMethod: 'eth_signTypedData_v4',
+      origin: 'https://metamask.github.io',
+      metamaskId: 'fb2029e0-b0ab-11ef-9227-05a11087c334',
+      meta: {
+        url: 'https://metamask.github.io/test-dapp/',
+        title: 'E2E Test Dapp',
+        icon: { uri: 'https://metamask.github.io/metamask-fox.svg' },
+        analytics: { request_source: 'In-App-Browser' },
+      },
+    },
+    networkClientId: '1',
+    status: SignatureRequestStatus.Unapproved,
+    time: 1733143817088,
+  } as SignatureRequest;
+
+  return {
+    engine: {
+      backgroundState: {
+        ...backgroundState,
+        ApprovalController: {
+          pendingApprovals: {
+            'c5067710-87cf-11ef-916c-71f266571322': {
+              id: 'c5067710-87cf-11ef-916c-71f266571322',
+              origin: 'metamask.github.io',
+              type: SignatureRequestType.TypedSign,
+              time: 1733143817088,
+              requestData: { ...mockSignatureRequest },
+              requestState: null,
+              expectsResult: true,
+            },
+          },
+          pendingApprovalCount: 1,
+          approvalFlows: [],
+        },
+        SignatureController: {
+          signatureRequests: {
+            'c5067710-87cf-11ef-916c-71f266571322':
+            mockSignatureRequest,
+          },
+        },
+      },
+    },
+  };
+}
+
diff --git a/locales/languages/en.json b/locales/languages/en.json
index 42c87bdda12..23399b2e41d 100644
--- a/locales/languages/en.json
+++ b/locales/languages/en.json
@@ -3647,6 +3647,7 @@
     "title": {
       "signature": "Signature request",
       "permit": "Spending cap request",
+      "permit_revoke": "Remove permission",
       "permit_NFTs": "Withdrawal request",
       "signature_siwe": "Sign-in request"
     },
@@ -3680,6 +3681,7 @@
       "decoded_tooltip_bid_nft": "The NFT will be reflected in your wallet, when the bid is accepted.",
       "decoded_tooltip_list_nft": "Expect changes only if someone buys your NFTs.",
       "info_permit": "You're giving the spender permission to spend this many tokens from your account.",
+      "info_revoke": "You're removing someone's permission to spend tokens from your account.",
       "label_change_type_bidding": "You bid",
       "label_change_type_listing": "You list",
       "label_change_type_nft_listing": "Listing price",


Reviews:
user: login: pedronfigueiredo
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T16:01:00Z
user: login: digiwand
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T08:24:53Z
user: login: OGPoyraz
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-05T14:31:27Z
user: login: digiwand
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T16:43:47Z
user: login: digiwand
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T17:00:37Z
user: login: digiwand
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T17:03:26Z
user: login: OGPoyraz
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-05T17:09:50Z
user: login: georgewrmarshall
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T23:57:16Z
user: login: georgewrmarshall
body: Approved on behalf of @MetaMask/design-system-engineers 
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T23:58:12Z
user: login: digiwand
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T00:05:22Z
user: login: OGPoyraz
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-07T07:33:17Z
user: login: georgewrmarshall
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T17:12:46Z

Review comments:
diff_hunk: @@ -38,7 +38,6 @@ const styleSheet = (params: {
       paddingHorizontal: 8,
       paddingVertical: 4,
       borderRadius: 99,
-      opacity: isDisabled ? 0.5 : 1,
path: app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.styles.ts
user: login: digiwand
body: hi @MetaMask/design-system-engineers, 

I'm removing the isDisabled opacity from this components-temp component since we don't have a use case for this yet.  isDisabled will continue to disable the isPressed action + style. 
created_at: 2025-03-05T08:24:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,20 +33,33 @@ const getTitleAndSubTitle = (approvalRequest?: ApprovalRequest<{ data: string }>
       const isPermit = isRecognizedPermit(signatureRequest);
 
       if (isPermit) {
-        const { message } = parseTypedDataMessageFromSignatureRequest(signatureRequest);
-        const isERC721Permit = message?.tokenId !== undefined;
+        const {
+          domain: { verifyingContract },
+          message
+        } = parseTypedDataMessageFromSignatureRequest(signatureRequest) ?? {};
path: app/components/Views/confirmations/components/Confirm/Title/Title.tsx
user: login: OGPoyraz
body: This will cause a TypeError if `parseTypedDataMessageFromSignatureRequest(signatureRequest) ?? {}` evaluated as `{}`. Because `domain` will be undefined.

![Screenshot 2025-03-05 at 15 21 31](https://github.com/user-attachments/assets/3b337487-7a9a-407a-98d7-af96e44d1727)

created_at: 2025-03-05T14:22:20Z
author_association: MEMBER
diff_hunk: @@ -130,7 +150,7 @@ describe('DecodedSimulation', () => {
     expect(await getByText('Estimated changes')).toBeDefined();
     expect(await getByText('Revoke')).toBeDefined();
 
-    await waitFor(() => expect(getByText('12,345')).toBeDefined());
+    await waitFor(() => expect(getByText('Unlimited')).toBeDefined());
path: app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx
user: login: OGPoyraz
body: Why should we expect `Unlimited` here if didn't change anything in `stateChangesRevoke`?
created_at: 2025-03-05T14:27:40Z
author_association: MEMBER
diff_hunk: @@ -65,53 +65,69 @@ const PermitSimulation = () => {
   const {
     domain: { verifyingContract },
     message,
-    message: { tokenId },
+    message: { allowed, tokenId, value },
     primaryType,
   } = parseTypedDataMessage(msgData as string);
 
   const tokenDetails = extractTokenDetailsByPrimaryType(message, primaryType);
 
   const isNFT = tokenId !== undefined && tokenId !== '0';
 
-  const labelChangeType = isNFT
+  let labelChangeType = isNFT
     ? strings('confirm.simulation.label_change_type_permit_nft')
-    : strings('confirm.simulation.label_change_type_permit');
+      : strings('confirm.simulation.label_change_type_permit');
+
+  const isDaiRevoke = isPermitDaiRevoke(verifyingContract, allowed, value);
+
+  if (isDaiRevoke) {
+    labelChangeType = strings('confirm.simulation.label_change_type_revoke');
+  }
+
+  const infoText = isDaiRevoke
+    ? strings('confirm.simulation.info_revoke')
+    : strings('confirm.simulation.info_permit');
 
   return (
     <InfoSection>
       <InfoRow
         label={strings('confirm.simulation.title')}
         tooltip={strings('confirm.simulation.tooltip')}
       >
-        {strings('confirm.simulation.info_permit')}
+        {infoText}
       </InfoRow>
 
       <InfoRow label={labelChangeType}>
         {Array.isArray(tokenDetails) ? (
           <>
             {tokenDetails.map(
               (
-                { token, amount }: { token: string; amount: string },
+                { allowed: tokenDetailAllowed, token, amount }: { allowed: string, token: string; amount: string },
path: app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.tsx
user: login: OGPoyraz
body: Minor, would it be cleaner if we tell this type in the return type of `extractTokenDetailsByPrimaryType`?
created_at: 2025-03-05T14:29:49Z
author_association: MEMBER
diff_hunk: @@ -33,20 +33,33 @@ const getTitleAndSubTitle = (approvalRequest?: ApprovalRequest<{ data: string }>
       const isPermit = isRecognizedPermit(signatureRequest);
 
       if (isPermit) {
-        const { message } = parseTypedDataMessageFromSignatureRequest(signatureRequest);
-        const isERC721Permit = message?.tokenId !== undefined;
+        const {
+          domain: { verifyingContract },
+          message
+        } = parseTypedDataMessageFromSignatureRequest(signatureRequest) ?? {};
path: app/components/Views/confirmations/components/Confirm/Title/Title.tsx
user: login: digiwand
body: updated https://github.com/MetaMask/metamask-mobile/pull/13666/commits/a185447ddca0ad5535b7c5831beffa356a1ebcbf
created_at: 2025-03-05T16:43:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -130,7 +150,7 @@ describe('DecodedSimulation', () => {
     expect(await getByText('Estimated changes')).toBeDefined();
     expect(await getByText('Revoke')).toBeDefined();
 
-    await waitFor(() => expect(getByText('12,345')).toBeDefined());
+    await waitFor(() => expect(getByText('Unlimited')).toBeDefined());
path: app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignDecoded/TypedSignDecoded.test.tsx
user: login: digiwand
body: I agree. I did it for DAI revoke. I will extend to the other revoke instances

https://github.com/MetaMask/metamask-mobile/pull/13666/commits/90067cb2cde198c1e50d6eaa4a251d394fecfe45
created_at: 2025-03-05T17:00:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -65,53 +65,69 @@ const PermitSimulation = () => {
   const {
     domain: { verifyingContract },
     message,
-    message: { tokenId },
+    message: { allowed, tokenId, value },
     primaryType,
   } = parseTypedDataMessage(msgData as string);
 
   const tokenDetails = extractTokenDetailsByPrimaryType(message, primaryType);
 
   const isNFT = tokenId !== undefined && tokenId !== '0';
 
-  const labelChangeType = isNFT
+  let labelChangeType = isNFT
     ? strings('confirm.simulation.label_change_type_permit_nft')
-    : strings('confirm.simulation.label_change_type_permit');
+      : strings('confirm.simulation.label_change_type_permit');
+
+  const isDaiRevoke = isPermitDaiRevoke(verifyingContract, allowed, value);
+
+  if (isDaiRevoke) {
+    labelChangeType = strings('confirm.simulation.label_change_type_revoke');
+  }
+
+  const infoText = isDaiRevoke
+    ? strings('confirm.simulation.info_revoke')
+    : strings('confirm.simulation.info_permit');
 
   return (
     <InfoSection>
       <InfoRow
         label={strings('confirm.simulation.title')}
         tooltip={strings('confirm.simulation.tooltip')}
       >
-        {strings('confirm.simulation.info_permit')}
+        {infoText}
       </InfoRow>
 
       <InfoRow label={labelChangeType}>
         {Array.isArray(tokenDetails) ? (
           <>
             {tokenDetails.map(
               (
-                { token, amount }: { token: string; amount: string },
+                { allowed: tokenDetailAllowed, token, amount }: { allowed: string, token: string; amount: string },
path: app/components/Views/confirmations/components/Confirm/Info/TypedSignV3V4/Simulation/TypedSignPermit/TypedSignPermit.tsx
user: login: digiwand
body: lgtm! updated https://github.com/MetaMask/metamask-mobile/pull/13666/commits/a499429e52bbfca2f2f555473eaac8f29ea2f738
created_at: 2025-03-05T17:03:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -38,7 +38,6 @@ const styleSheet = (params: {
       paddingHorizontal: 8,
       paddingVertical: 4,
       borderRadius: 99,
-      opacity: isDisabled ? 0.5 : 1,
path: app/component-library/components-temp/Buttons/ButtonPill/ButtonPill.styles.ts
user: login: georgewrmarshall
body: Sounds good!
created_at: 2025-03-05T23:57:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -75,17 +76,11 @@ jest.mock('react-native-gzip', () => ({
   deflate: (str: string) => str,
 }));
 
-jest.mock('@react-navigation/native', () => ({
-  ...jest.requireActual('@react-navigation/native'),
-  useNavigation: () => ({
-    navigate: jest.fn(),
-    addListener: jest.fn(),
-    dispatch: jest.fn(),
-    setOptions: jest.fn(),
-  }),
-}));
-
path: app/components/Views/confirmations/Confirm/Confirm.test.tsx
user: login: digiwand
body: this is a duplicate mock of '@react-navigation/native' that was added
created_at: 2025-03-07T00:05:22Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-02-21T14:43:26Z
body: **CLA Signature Action:** All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-02-24T18:33:45Z
body: ## [<img alt="https://bitrise.io/" src="https://assets-global.website-files.com/5db35de024bb983af1b4e151/5e6f9ccc3e129dfd8a205e4e_Bitrise%20Logo%20-%20Eggplant%20Bg.png" height="20">](https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/a14b3b8a-76f6-4d68-a0b3-b7d2bde5c649) **Bitrise**

🔄🔄🔄 `pr_smoke_e2e_pipeline` started on Bitrise...🔄🔄🔄

Commit hash: 8c21348f05ce3057ba04196b84ae782168f94394
Build link: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/a14b3b8a-76f6-4d68-a0b3-b7d2bde5c649

>[!NOTE]
>- This comment will auto-update when build completes
>- You can kick off another `pr_smoke_e2e_pipeline` on Bitrise by removing and re-applying the `Run Smoke E2E` label on the pull request
<!-- BITRISE_TAG -->
<!-- BITRISE_PENDING_TAG -->

<!-- 8c21348f05ce3057ba04196b84ae782168f94394 -->
author_association: CONTRIBUTOR
user: login: codecov-commenter
created_at: 2025-03-05T04:56:42Z
body: ## [Codecov](https://app.codecov.io/gh/MetaMask/metamask-mobile/pull/13666?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 63.73%. Comparing base [(`d1d22d7`)](https://app.codecov.io/gh/MetaMask/metamask-mobile/commit/d1d22d72d3871336b1b25eb2de191dea407aae77?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask) to head [(`7601941`)](https://app.codecov.io/gh/MetaMask/metamask-mobile/commit/7601941ca497f488603f28e019f50c67863f7125?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask).
> Report is 24 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main   #13666      +/-   ##
==========================================
+ Coverage   63.42%   63.73%   +0.31%     
==========================================
  Files        2093     2113      +20     
  Lines       45277    45458     +181     
  Branches     6234     6266      +32     
==========================================
+ Hits        28717    28973     +256     
+ Misses      14676    14604      -72     
+ Partials     1884     1881       -3     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/MetaMask/metamask-mobile/pull/13666?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MetaMask).

author_association: NONE
user: login: digiwand
created_at: 2025-03-07T00:07:33Z
body: hey @OGPoyraz and @georgewrmarshall, thanks for the reviews! Apologies, I got hung up on fixing a test. 

Seemed to be caused after I mocked a keyring value, so I removed this. 
```
./Users/ariella/git/MetaMask/metamask-mobile/node_modules/@testing-library/react-native/build/screen.js:11
  throw new Error(SCREEN_ERROR);
  ^

Error: `render` method has not been called
    at Object.notImplemented (/Users/ariella/git/MetaMask/metamask-mobile/node_modules/@testing-library/react-native/src/screen.ts:7:9)
    at toJSON (/Users/ariella/git/MetaMask/metamask-mobile/node_modules/@testing-library/react-native/src/queries/makeQueries.ts:94:23)
    at formatErrorMessage (/Users/ariella/git/MetaMask/metamask-mobile/node_modules/@testing-library/react-native/src/queries/makeQueries.ts:106:19)
    at appendElementTreeToError (/Users/ariella/git/MetaMask/metamask-mobile/node_modules/@testing-library/react-native/src/queries/makeQueries.ts:223:32)
    at Timeout.onTimeout [as _onTimeout] (/Users/ariella/git/MetaMask/metamask-mobile/node_modules/@testing-library/react-native/src/waitFor.ts:185:24)
    at listOnTimeout (node:internal/timers:581:17)
    at processTimers (node:internal/timers:519:7
```

ready for review again
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-03-07T00:15:54Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=metamask-mobile&pullRequest=13666) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [1 New issue](https://sonarcloud.io/project/issues?id=metamask-mobile&pullRequest=13666&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=metamask-mobile&pullRequest=13666&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=metamask-mobile&pullRequest=13666&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [85.5% Coverage on New Code](https://sonarcloud.io/component_measures?id=metamask-mobile&pullRequest=13666&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=metamask-mobile&pullRequest=13666&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=metamask-mobile&pullRequest=13666)


author_association: NONE