Skip to content
59 changes: 56 additions & 3 deletions app/scripts/lib/dapp-swap/dapp-swap-middleware.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { JsonRpcResponseStruct } from '@metamask/utils';

import { flushPromises } from '../../../../test/lib/timer-helpers';
Expand All @@ -20,15 +21,23 @@ const getNetworkConfigurationByNetworkClientId = jest.fn();
const createMiddleware = (
args: {
// eslint-disable-next-line @typescript-eslint/naming-convention
dappSwapMetricsFlag?: { enabled: boolean; bridge_quote_fees: number };
dappSwapMetricsFlag?: {
enabled: boolean;
bridge_quote_fees: number;
origins: string[];
};
} = {},
) => {
const middlewareFunction = createDappSwapMiddleware({
fetchQuotes,
setDappSwapComparisonData,
getNetworkConfigurationByNetworkClientId,
// eslint-disable-next-line @typescript-eslint/naming-convention
dappSwapMetricsFlag: { enabled: true, bridge_quote_fees: 250 },
dappSwapMetricsFlag: {
enabled: true,
bridge_quote_fees: 250,
origins: ['https://metamask.github.io'],
},
...args,
});
return { middlewareFunction };
Expand Down Expand Up @@ -60,7 +69,11 @@ describe('DappSwapMiddleware', () => {
fetchQuotes.mockReturnValueOnce(mockBridgeQuotes);
const { middlewareFunction } = createMiddleware({
// eslint-disable-next-line @typescript-eslint/naming-convention
dappSwapMetricsFlag: { enabled: false, bridge_quote_fees: 250 },
dappSwapMetricsFlag: {
enabled: false,
bridge_quote_fees: 250,
origins: ['https://metamask.github.io'],
},
});

const req = {
Expand Down Expand Up @@ -221,4 +234,44 @@ describe('DappSwapMiddleware', () => {
'Error fetching bridge quotes: Error: Error getting data from swap: invalid batch transaction, invalid command',
});
});

it('does not fetch quotes if origin is not in the list of allowed origins', async () => {
fetchQuotes.mockReturnValueOnce(mockBridgeQuotes);
const { middlewareFunction } = createMiddleware({
// eslint-disable-next-line @typescript-eslint/naming-convention
dappSwapMetricsFlag: {
enabled: true,
bridge_quote_fees: 250,
origins: ['https://metamask.github.io'],
},
});

const req = {
...REQUEST_MOCK,
method: 'eth_sendTransaction',
origin: 'https://test.com',
securityAlertResponse: {
securityAlertId: '123',
},
params: [
{
data: '0x3593564c000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000068f0dd1b0000000000000000000000000000000000000000000000000000000000000003100604000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000044000000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000003c0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003070b0e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000005af3107a4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e58310000000000000000000000007ffc3dbf3b2b50ff3a1d5523bc24bb5043837b1400000000000000000000000000000000000000000000000000000000000000190000000000000000000000000000000000000000000000000000000000000060000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000178239802520a9c99dcbd791f81326b70298d62900000000000000000000000000000000000000000000000000000000000601470c',
from: '0x12312312312312',
chainId: '1',
calls: [],
},
],
networkClientId: 'networkClientId',
};

await middlewareFunction(
req as unknown as DappSwapMiddlewareRequest<(string | { to: string })[]>,
{ ...JsonRpcResponseStruct.TYPE },
() => undefined,
);

await flushPromises();

expect(fetchQuotes).not.toHaveBeenCalled();
});
});
8 changes: 6 additions & 2 deletions app/scripts/lib/dapp-swap/dapp-swap-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,12 @@ export function createDappSwapMiddleware<
getNetworkConfigurationByNetworkClientId: (
networkClientId: NetworkClientId,
) => NetworkConfiguration | undefined;
// eslint-disable-next-line @typescript-eslint/naming-convention
dappSwapMetricsFlag: { enabled: boolean; bridge_quote_fees: number };
dappSwapMetricsFlag: {
enabled: boolean;
// eslint-disable-next-line @typescript-eslint/naming-convention
bridge_quote_fees: number;
origins: string[];
};
}) {
return async (
req: DappSwapMiddlewareRequest<Params>,
Expand Down
21 changes: 13 additions & 8 deletions app/scripts/lib/dapp-swap/dapp-swap-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ export type DappSwapMiddlewareRequest<
};

const FOUR_BYTE_EXECUTE_SWAP_CONTRACT = '0x3593564c';
const DAPP_SWAP_COMPARISON_ORIGIN = 'https://app.uniswap.org';
const TEST_DAPP_ORIGIN = 'https://metamask.github.io';

const getSwapDetails = (params: DappSwapMiddlewareRequest['params']) => {
if (!params?.length) {
Expand Down Expand Up @@ -79,20 +77,27 @@ export function getQuotesForConfirmation({
getNetworkConfigurationByNetworkClientId: (
networkClientId: NetworkClientId,
) => NetworkConfiguration | undefined;
// eslint-disable-next-line @typescript-eslint/naming-convention
dappSwapMetricsFlag: { enabled: boolean; bridge_quote_fees: number };
dappSwapMetricsFlag: {
enabled: boolean;
// eslint-disable-next-line @typescript-eslint/naming-convention
bridge_quote_fees: number;
origins: string[];
};
securityAlertId?: string;
}) {
let commands = '';
try {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { enabled: dappSwapEnabled, bridge_quote_fees: bridgeQuoteFees } =
dappSwapMetricsFlag;
const {
enabled: dappSwapEnabled,
// eslint-disable-next-line @typescript-eslint/naming-convention
bridge_quote_fees: bridgeQuoteFees,
origins,
} = dappSwapMetricsFlag;
if (!dappSwapEnabled || !securityAlertId) {
return;
}
const { params, origin } = req;
if (origin === DAPP_SWAP_COMPARISON_ORIGIN || origin === TEST_DAPP_ORIGIN) {
if (origin && origins.includes(origin)) {
const { chainId } =
getNetworkConfigurationByNetworkClientId(req.networkClientId) ?? {};
const { data, from } = getSwapDetails(params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Confirmation } from '../../../types/confirm';
import { useDappSwapComparisonInfo } from '../../../hooks/transactions/dapp-swap-comparison/useDappSwapComparisonInfo';
import * as DappSwapContext from '../../../context/dapp-swap';
import { useDappSwapComparisonRewardText } from '../../../hooks/transactions/dapp-swap-comparison/useDappSwapComparisonRewardText';
import { useDappSwapCheck } from '../../../hooks/transactions/dapp-swap-comparison/useDappSwapCheck';
import { DappSwapComparisonBanner } from './dapp-swap-comparison-banner';

jest.mock(
Expand Down Expand Up @@ -53,6 +54,15 @@ jest.mock(
}),
);

jest.mock(
'../../../hooks/transactions/dapp-swap-comparison/useDappSwapCheck',
() => ({
useDappSwapCheck: jest.fn(() => ({
isSwapToBeCompared: true,
})),
}),
);

jest.mock('../../../../../selectors/remote-feature-flags');

const quote = {
Expand Down Expand Up @@ -107,6 +117,7 @@ function render(args: Record<string, string> = {}) {
}

describe('<DappSwapComparisonBanner />', () => {
const mockUseDappSwapCheck = jest.mocked(useDappSwapCheck);
const mockGetRemoteFeatureFlags = jest.mocked(getRemoteFeatureFlags);
const mockUseDappSwapComparisonInfo = jest.mocked(useDappSwapComparisonInfo);
const mockUseDappSwapComparisonRewardText = jest.mocked(
Expand All @@ -120,6 +131,9 @@ describe('<DappSwapComparisonBanner />', () => {
dappSwapUi: { enabled: true, threshold: 0.01 },
});
mockUseDappSwapComparisonRewardText.mockReturnValue(null);
mockUseDappSwapCheck.mockReturnValue({
isSwapToBeCompared: true,
});
});

it('renders component without errors', () => {
Expand All @@ -139,18 +153,13 @@ describe('<DappSwapComparisonBanner />', () => {
});

it('renders undefined for incorrect origin', () => {
mockUseDappSwapCheck.mockReturnValue({
isSwapToBeCompared: false,
});
const { container } = render({ origin: 'www.test.com' });
expect(container).toBeEmptyDOMElement();
});

it('renders undefined if suitable quote is not found', () => {
mockUseDappSwapComparisonInfo.mockReturnValue({
selectedQuoteValueDifference: 0.001,
} as ReturnType<typeof useDappSwapComparisonInfo>);
const { container } = render();
expect(container).toBeEmptyDOMElement();
});

it('call function to update quote swap when user clicks on Metamask Swap button', () => {
const mockSetQuotedSwapDisplayedInInfo = jest.fn();
jest.spyOn(DappSwapContext, 'useDappSwapContext').mockReturnValue({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,9 @@ const DappSwapComparisonInner = () => {
};

export const DappSwapComparisonBanner = () => {
const { dappSwapMetrics } = useSelector(getRemoteFeatureFlags);
const { isSwapToBeCompared } = useDappSwapCheck();

const dappSwapMetricsEnabled =
(dappSwapMetrics as { enabled: boolean })?.enabled === true &&
isSwapToBeCompared;

if (!dappSwapMetricsEnabled) {
if (!isSwapToBeCompared) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ describe('<BaseTransactionInfo />', () => {
selectedQuote: undefined,
setSelectedQuote: jest.fn(),
setQuotedSwapDisplayedInInfo: jest.fn(),
isQuotedSwapPresent: false,
} as ReturnType<typeof DappSwapContext.useDappSwapContext>);

const { getByText, queryByText } = renderWithConfirmContextProvider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ describe('useFeeCalculations', () => {
setSelectedQuote: jest.fn(),
setQuotedSwapDisplayedInInfo: jest.fn(),
isQuotedSwapDisplayedInInfo: true,
isQuotedSwapPresent: true,
} as DappSwapContext.DappSwapContextType);
const transactionMeta = genUnapprovedContractInteractionConfirmation({
address: CONTRACT_INTERACTION_SENDER_ADDRESS,
Expand Down
2 changes: 2 additions & 0 deletions ui/pages/confirmations/context/dapp-swap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import useCurrentConfirmation from '../../hooks/useCurrentConfirmation';

export type DappSwapContextType = {
isQuotedSwapDisplayedInInfo: boolean;
isQuotedSwapPresent: boolean;
selectedQuote: QuoteResponse | undefined;
setSelectedQuote: (selectedQuote: QuoteResponse | undefined) => void;
setQuotedSwapDisplayedInInfo: (isQuotedSwapDisplayedInInfo: boolean) => void;
Expand Down Expand Up @@ -39,6 +40,7 @@ export const DappSwapContextProvider: React.FC<{
const value = useMemo(
() => ({
isQuotedSwapDisplayedInInfo,
isQuotedSwapPresent: selectedQuote !== undefined,
selectedQuote,
setSelectedQuote,
setQuotedSwapDisplayedInInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,25 @@ import { renderHookWithConfirmContextProvider } from '../../../../../../test/lib
import { Confirmation } from '../../../types/confirm';
import { useDappSwapCheck } from './useDappSwapCheck';

async function runHook(mockConfirmation?: Confirmation) {
async function runHook(
mockConfirmation?: Confirmation,
dappSwapMetrics: { enabled?: boolean; origins?: string[] } = {},
) {
const response = renderHookWithConfirmContextProvider(
useDappSwapCheck,
getMockConfirmStateForTransaction(
mockConfirmation ?? (mockSwapConfirmation as Confirmation),
{
metamask: {
remoteFeatureFlags: {
dappSwapMetrics: {
enabled: true,
origins: ['https://metamask.github.io'],
...dappSwapMetrics,
},
},
},
},
),
);

Expand All @@ -23,7 +37,7 @@ async function runHook(mockConfirmation?: Confirmation) {
}

describe('useDappSwapCheck', () => {
it('return correct value for isSwapToBeCompared', async () => {
it('return true if dappSwapMetrics is enabled and origin is in the list of allowed origins and type is contract interaction or batch', async () => {
const mockConfirmation = {
...mockSwapConfirmation,
origin: 'https://metamask.github.io',
Expand All @@ -35,15 +49,41 @@ describe('useDappSwapCheck', () => {
expect(isSwapToBeCompared).toBe(true);
});

it('return correct value for isSwapToBeCompared', async () => {
it('return false if dappSwapMetrics is not enabled', async () => {
const mockConfirmation = {
...mockSwapConfirmation,
origin: 'https://metamask.github.io',
type: TransactionType.contractInteraction,
};
const { isSwapToBeCompared } = await runHook(
mockConfirmation as Confirmation,
{ enabled: false },
);
expect(isSwapToBeCompared).toBe(true);
expect(isSwapToBeCompared).toBe(false);
});

it('return false if origin is not in the list of allowed origins', async () => {
const mockConfirmation = {
...mockSwapConfirmation,
origin: 'https://test.com',
type: TransactionType.contractInteraction,
};
const { isSwapToBeCompared } = await runHook(
mockConfirmation as Confirmation,
{ origins: ['https://metamask.github.io'] },
);
expect(isSwapToBeCompared).toBe(false);
});

it('return false if type is not contract interaction or batch', async () => {
const mockConfirmation = {
...mockSwapConfirmation,
origin: 'https://metamask.github.io',
type: TransactionType.bridge,
};
const { isSwapToBeCompared } = await runHook(
mockConfirmation as Confirmation,
);
expect(isSwapToBeCompared).toBe(false);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,31 @@ import {
TransactionType,
} from '@metamask/transaction-controller';
import { useMemo } from 'react';
import { useSelector } from 'react-redux';

import { getRemoteFeatureFlags } from '../../../../../selectors/remote-feature-flags';
import { useConfirmContext } from '../../../context/confirm';

const DAPP_SWAP_COMPARISON_ORIGIN = 'https://app.uniswap.org';
const TEST_DAPP_ORIGIN = 'https://metamask.github.io';

export function useDappSwapCheck() {
const { currentConfirmation } = useConfirmContext<TransactionMeta>();
const {
dappSwapMetrics: { enabled: dappSwapMetricsEnabled, origins = [] } = {},
} = useSelector(getRemoteFeatureFlags) as {
dappSwapMetrics: { enabled: boolean; origins: string[] };
};
const { origin, type } = currentConfirmation ?? {
txParams: { data: '' },
};

const isSwapToBeCompared = useMemo(() => {
return (
(origin === DAPP_SWAP_COMPARISON_ORIGIN || origin === TEST_DAPP_ORIGIN) &&
dappSwapMetricsEnabled &&
origin !== undefined &&
origins.includes(origin) &&
(type === TransactionType.contractInteraction ||
type === TransactionType.batch)
);
}, [origin, type]);
}, [dappSwapMetricsEnabled, origin, origins, type]);

return {
isSwapToBeCompared,
Expand Down
Loading
Loading