mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Event tracking for Token Detection V2 (#14441)
This commit is contained in:
parent
4b2cd0ef7a
commit
6c757ab5e0
@ -6,6 +6,9 @@ import { MINUTE } from '../../../shared/constants/time';
|
|||||||
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network';
|
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network';
|
||||||
import { isTokenDetectionEnabledForNetwork } from '../../../shared/modules/network.utils';
|
import { isTokenDetectionEnabledForNetwork } from '../../../shared/modules/network.utils';
|
||||||
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
|
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
|
||||||
|
import { TOKEN_STANDARDS } from '../../../ui/helpers/constants/common';
|
||||||
|
import { ASSET_TYPES } from '../../../shared/constants/transaction';
|
||||||
|
import { EVENT, EVENT_NAMES } from '../../../shared/constants/metametrics';
|
||||||
|
|
||||||
// By default, poll every 3 minutes
|
// By default, poll every 3 minutes
|
||||||
const DEFAULT_INTERVAL = MINUTE * 3;
|
const DEFAULT_INTERVAL = MINUTE * 3;
|
||||||
@ -26,6 +29,7 @@ export default class DetectTokensController {
|
|||||||
* @param config.tokenList
|
* @param config.tokenList
|
||||||
* @param config.tokensController
|
* @param config.tokensController
|
||||||
* @param config.assetsContractController
|
* @param config.assetsContractController
|
||||||
|
* @param config.trackMetaMetricsEvent
|
||||||
*/
|
*/
|
||||||
constructor({
|
constructor({
|
||||||
interval = DEFAULT_INTERVAL,
|
interval = DEFAULT_INTERVAL,
|
||||||
@ -35,6 +39,7 @@ export default class DetectTokensController {
|
|||||||
tokenList,
|
tokenList,
|
||||||
tokensController,
|
tokensController,
|
||||||
assetsContractController = null,
|
assetsContractController = null,
|
||||||
|
trackMetaMetricsEvent,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.assetsContractController = assetsContractController;
|
this.assetsContractController = assetsContractController;
|
||||||
this.tokensController = tokensController;
|
this.tokensController = tokensController;
|
||||||
@ -51,6 +56,7 @@ export default class DetectTokensController {
|
|||||||
this.detectedTokens = process.env.TOKEN_DETECTION_V2
|
this.detectedTokens = process.env.TOKEN_DETECTION_V2
|
||||||
? this.tokensController?.state.detectedTokens
|
? this.tokensController?.state.detectedTokens
|
||||||
: [];
|
: [];
|
||||||
|
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
|
||||||
|
|
||||||
preferences?.store.subscribe(({ selectedAddress, useTokenDetection }) => {
|
preferences?.store.subscribe(({ selectedAddress, useTokenDetection }) => {
|
||||||
if (
|
if (
|
||||||
@ -162,6 +168,7 @@ export default class DetectTokensController {
|
|||||||
|
|
||||||
let tokensWithBalance = [];
|
let tokensWithBalance = [];
|
||||||
if (process.env.TOKEN_DETECTION_V2) {
|
if (process.env.TOKEN_DETECTION_V2) {
|
||||||
|
const eventTokensDetails = [];
|
||||||
if (result) {
|
if (result) {
|
||||||
const nonZeroTokenAddresses = Object.keys(result);
|
const nonZeroTokenAddresses = Object.keys(result);
|
||||||
for (const nonZeroTokenAddress of nonZeroTokenAddresses) {
|
for (const nonZeroTokenAddress of nonZeroTokenAddresses) {
|
||||||
@ -172,6 +179,9 @@ export default class DetectTokensController {
|
|||||||
iconUrl,
|
iconUrl,
|
||||||
aggregators,
|
aggregators,
|
||||||
} = tokenList[nonZeroTokenAddress];
|
} = tokenList[nonZeroTokenAddress];
|
||||||
|
|
||||||
|
eventTokensDetails.push(`${symbol} - ${address}`);
|
||||||
|
|
||||||
tokensWithBalance.push({
|
tokensWithBalance.push({
|
||||||
address,
|
address,
|
||||||
symbol,
|
symbol,
|
||||||
@ -180,7 +190,17 @@ export default class DetectTokensController {
|
|||||||
aggregators,
|
aggregators,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokensWithBalance.length > 0) {
|
if (tokensWithBalance.length > 0) {
|
||||||
|
this._trackMetaMetricsEvent({
|
||||||
|
event: EVENT_NAMES.TOKEN_DETECTED,
|
||||||
|
category: EVENT.CATEGORIES.WALLET,
|
||||||
|
properties: {
|
||||||
|
tokens: eventTokensDetails,
|
||||||
|
token_standard: TOKEN_STANDARDS.ERC20,
|
||||||
|
asset_type: ASSET_TYPES.TOKEN,
|
||||||
|
},
|
||||||
|
});
|
||||||
await this.tokensController.addDetectedTokens(tokensWithBalance);
|
await this.tokensController.addDetectedTokens(tokensWithBalance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -571,6 +571,7 @@ export default class MetaMetricsController {
|
|||||||
[TRAITS.OPENSEA_API_ENABLED]: metamaskState.openSeaEnabled,
|
[TRAITS.OPENSEA_API_ENABLED]: metamaskState.openSeaEnabled,
|
||||||
[TRAITS.THREE_BOX_ENABLED]: metamaskState.threeBoxSyncingAllowed,
|
[TRAITS.THREE_BOX_ENABLED]: metamaskState.threeBoxSyncingAllowed,
|
||||||
[TRAITS.THEME]: metamaskState.theme || 'default',
|
[TRAITS.THEME]: metamaskState.theme || 'default',
|
||||||
|
[TRAITS.TOKEN_DETECTION_ENABLED]: metamaskState.useTokenDetection,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.previousTraits) {
|
if (!this.previousTraits) {
|
||||||
|
@ -682,6 +682,7 @@ describe('MetaMetricsController', function () {
|
|||||||
threeBoxSyncingAllowed: false,
|
threeBoxSyncingAllowed: false,
|
||||||
useCollectibleDetection: false,
|
useCollectibleDetection: false,
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
|
useTokenDetection: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(traits, {
|
assert.deepEqual(traits, {
|
||||||
@ -696,6 +697,7 @@ describe('MetaMetricsController', function () {
|
|||||||
[TRAITS.OPENSEA_API_ENABLED]: true,
|
[TRAITS.OPENSEA_API_ENABLED]: true,
|
||||||
[TRAITS.THREE_BOX_ENABLED]: false,
|
[TRAITS.THREE_BOX_ENABLED]: false,
|
||||||
[TRAITS.THEME]: 'default',
|
[TRAITS.THEME]: 'default',
|
||||||
|
[TRAITS.TOKEN_DETECTION_ENABLED]: true,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -717,6 +719,7 @@ describe('MetaMetricsController', function () {
|
|||||||
threeBoxSyncingAllowed: false,
|
threeBoxSyncingAllowed: false,
|
||||||
useCollectibleDetection: false,
|
useCollectibleDetection: false,
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
|
useTokenDetection: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedTraits = metaMetricsController._buildUserTraitsObject({
|
const updatedTraits = metaMetricsController._buildUserTraitsObject({
|
||||||
@ -737,6 +740,7 @@ describe('MetaMetricsController', function () {
|
|||||||
threeBoxSyncingAllowed: false,
|
threeBoxSyncingAllowed: false,
|
||||||
useCollectibleDetection: false,
|
useCollectibleDetection: false,
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
|
useTokenDetection: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.deepEqual(updatedTraits, {
|
assert.deepEqual(updatedTraits, {
|
||||||
@ -765,6 +769,7 @@ describe('MetaMetricsController', function () {
|
|||||||
threeBoxSyncingAllowed: false,
|
threeBoxSyncingAllowed: false,
|
||||||
useCollectibleDetection: true,
|
useCollectibleDetection: true,
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
|
useTokenDetection: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedTraits = metaMetricsController._buildUserTraitsObject({
|
const updatedTraits = metaMetricsController._buildUserTraitsObject({
|
||||||
@ -783,6 +788,7 @@ describe('MetaMetricsController', function () {
|
|||||||
threeBoxSyncingAllowed: false,
|
threeBoxSyncingAllowed: false,
|
||||||
useCollectibleDetection: true,
|
useCollectibleDetection: true,
|
||||||
theme: 'default',
|
theme: 'default',
|
||||||
|
useTokenDetection: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.equal(updatedTraits, null);
|
assert.equal(updatedTraits, null);
|
||||||
|
@ -1470,7 +1470,7 @@ describe('Transaction Controller', function () {
|
|||||||
gas_edit_type: 'none',
|
gas_edit_type: 'none',
|
||||||
network: '42',
|
network: '42',
|
||||||
referrer: ORIGIN_METAMASK,
|
referrer: ORIGIN_METAMASK,
|
||||||
source: 'user',
|
source: EVENT.SOURCE.TRANSACTION.USER,
|
||||||
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
||||||
account_type: 'MetaMask',
|
account_type: 'MetaMask',
|
||||||
asset_type: ASSET_TYPES.NATIVE,
|
asset_type: ASSET_TYPES.NATIVE,
|
||||||
@ -1549,7 +1549,7 @@ describe('Transaction Controller', function () {
|
|||||||
gas_edit_type: 'none',
|
gas_edit_type: 'none',
|
||||||
network: '42',
|
network: '42',
|
||||||
referrer: ORIGIN_METAMASK,
|
referrer: ORIGIN_METAMASK,
|
||||||
source: 'user',
|
source: EVENT.SOURCE.TRANSACTION.USER,
|
||||||
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
||||||
account_type: 'MetaMask',
|
account_type: 'MetaMask',
|
||||||
asset_type: ASSET_TYPES.NATIVE,
|
asset_type: ASSET_TYPES.NATIVE,
|
||||||
@ -1638,7 +1638,7 @@ describe('Transaction Controller', function () {
|
|||||||
gas_edit_type: 'none',
|
gas_edit_type: 'none',
|
||||||
network: '42',
|
network: '42',
|
||||||
referrer: 'other',
|
referrer: 'other',
|
||||||
source: 'dapp',
|
source: EVENT.SOURCE.TRANSACTION.DAPP,
|
||||||
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
||||||
account_type: 'MetaMask',
|
account_type: 'MetaMask',
|
||||||
asset_type: ASSET_TYPES.NATIVE,
|
asset_type: ASSET_TYPES.NATIVE,
|
||||||
@ -1719,7 +1719,7 @@ describe('Transaction Controller', function () {
|
|||||||
gas_edit_type: 'none',
|
gas_edit_type: 'none',
|
||||||
network: '42',
|
network: '42',
|
||||||
referrer: 'other',
|
referrer: 'other',
|
||||||
source: 'dapp',
|
source: EVENT.SOURCE.TRANSACTION.DAPP,
|
||||||
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
||||||
account_type: 'MetaMask',
|
account_type: 'MetaMask',
|
||||||
asset_type: ASSET_TYPES.NATIVE,
|
asset_type: ASSET_TYPES.NATIVE,
|
||||||
@ -1800,7 +1800,7 @@ describe('Transaction Controller', function () {
|
|||||||
gas_edit_type: 'none',
|
gas_edit_type: 'none',
|
||||||
network: '42',
|
network: '42',
|
||||||
referrer: 'other',
|
referrer: 'other',
|
||||||
source: 'dapp',
|
source: EVENT.SOURCE.TRANSACTION.DAPP,
|
||||||
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
||||||
account_type: 'MetaMask',
|
account_type: 'MetaMask',
|
||||||
asset_type: ASSET_TYPES.NATIVE,
|
asset_type: ASSET_TYPES.NATIVE,
|
||||||
@ -1859,7 +1859,7 @@ describe('Transaction Controller', function () {
|
|||||||
properties: {
|
properties: {
|
||||||
network: '42',
|
network: '42',
|
||||||
referrer: 'other',
|
referrer: 'other',
|
||||||
source: 'dapp',
|
source: EVENT.SOURCE.TRANSACTION.DAPP,
|
||||||
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
||||||
chain_id: '0x2a',
|
chain_id: '0x2a',
|
||||||
eip_1559_version: '0',
|
eip_1559_version: '0',
|
||||||
@ -1936,7 +1936,7 @@ describe('Transaction Controller', function () {
|
|||||||
gas_edit_type: 'none',
|
gas_edit_type: 'none',
|
||||||
network: '42',
|
network: '42',
|
||||||
referrer: 'other',
|
referrer: 'other',
|
||||||
source: 'dapp',
|
source: EVENT.SOURCE.TRANSACTION.DAPP,
|
||||||
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
type: TRANSACTION_TYPES.SIMPLE_SEND,
|
||||||
account_type: 'MetaMask',
|
account_type: 'MetaMask',
|
||||||
asset_type: ASSET_TYPES.NATIVE,
|
asset_type: ASSET_TYPES.NATIVE,
|
||||||
|
@ -265,7 +265,7 @@ async function addEthereumChainHandler(
|
|||||||
network: firstValidRPCUrl,
|
network: firstValidRPCUrl,
|
||||||
symbol: ticker,
|
symbol: ticker,
|
||||||
block_explorer_url: firstValidBlockExplorerUrl,
|
block_explorer_url: firstValidBlockExplorerUrl,
|
||||||
source: 'dapp',
|
source: EVENT.SOURCE.TRANSACTION.DAPP,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -713,6 +713,9 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
network: this.networkController,
|
network: this.networkController,
|
||||||
keyringMemStore: this.keyringController.memStore,
|
keyringMemStore: this.keyringController.memStore,
|
||||||
tokenList: this.tokenListController,
|
tokenList: this.tokenListController,
|
||||||
|
trackMetaMetricsEvent: this.metaMetricsController.trackEvent.bind(
|
||||||
|
this.metaMetricsController,
|
||||||
|
),
|
||||||
}))
|
}))
|
||||||
: (this.detectTokensController = new DetectTokensController({
|
: (this.detectTokensController = new DetectTokensController({
|
||||||
preferences: this.preferencesController,
|
preferences: this.preferencesController,
|
||||||
|
@ -178,6 +178,8 @@
|
|||||||
* @property {'three_box_enabled'} THREE_BOX_ENABLED - when 3box feature is
|
* @property {'three_box_enabled'} THREE_BOX_ENABLED - when 3box feature is
|
||||||
* toggled we identify the 3box_enabled trait
|
* toggled we identify the 3box_enabled trait
|
||||||
* @property {'theme'} THEME - when the user's theme changes we identify the theme trait
|
* @property {'theme'} THEME - when the user's theme changes we identify the theme trait
|
||||||
|
* @property {'token_detection_enabled'} TOKEN_DETECTION_ENABLED - when token detection feature is toggled we
|
||||||
|
* identify the token_detection_enabled trait
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -197,6 +199,7 @@ export const TRAITS = {
|
|||||||
OPENSEA_API_ENABLED: 'opensea_api_enabled',
|
OPENSEA_API_ENABLED: 'opensea_api_enabled',
|
||||||
THREE_BOX_ENABLED: 'three_box_enabled',
|
THREE_BOX_ENABLED: 'three_box_enabled',
|
||||||
THEME: 'theme',
|
THEME: 'theme',
|
||||||
|
TOKEN_DETECTION_ENABLED: 'token_detection_enabled',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -222,6 +225,7 @@ export const TRAITS = {
|
|||||||
* @property {boolean} [three_box_enabled] - does the user have 3box sync
|
* @property {boolean} [three_box_enabled] - does the user have 3box sync
|
||||||
* enabled?
|
* enabled?
|
||||||
* @property {string} [theme] - which theme the user has selected
|
* @property {string} [theme] - which theme the user has selected
|
||||||
|
* @property {boolean} [token_detection_enabled] - does the user have token detection is enabled?
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Mixpanel converts the zero address value to a truly anonymous event, which
|
// Mixpanel converts the zero address value to a truly anonymous event, which
|
||||||
@ -265,10 +269,15 @@ export const REJECT_NOTFICIATION_CLOSE_SIG =
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export const EVENT_NAMES = {
|
export const EVENT_NAMES = {
|
||||||
SIGNATURE_REQUESTED: 'Signature Requested',
|
|
||||||
ENCRYPTION_PUBLIC_KEY_REQUESTED: 'Encryption Public Key Requested',
|
ENCRYPTION_PUBLIC_KEY_REQUESTED: 'Encryption Public Key Requested',
|
||||||
DECRYPTION_REQUESTED: 'Decryption Requested',
|
DECRYPTION_REQUESTED: 'Decryption Requested',
|
||||||
PERMISSIONS_REQUESTED: 'Permissions Requested',
|
PERMISSIONS_REQUESTED: 'Permissions Requested',
|
||||||
|
SIGNATURE_REQUESTED: 'Signature Requested',
|
||||||
|
TOKEN_ADDED: 'Token Added',
|
||||||
|
TOKEN_DETECTED: 'Token Detected',
|
||||||
|
TOKEN_HIDDEN: 'Token Hidden',
|
||||||
|
TOKEN_IMPORT_CANCELED: 'Token Import Canceled',
|
||||||
|
TOKEN_IMPORT_CLICKED: 'Token Import Clicked',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EVENT = {
|
export const EVENT = {
|
||||||
@ -288,4 +297,25 @@ export const EVENT = {
|
|||||||
TRANSACTIONS: 'Transactions',
|
TRANSACTIONS: 'Transactions',
|
||||||
WALLET: 'Wallet',
|
WALLET: 'Wallet',
|
||||||
},
|
},
|
||||||
|
SOURCE: {
|
||||||
|
SWAPS: {
|
||||||
|
MAIN_VIEW: 'Main View',
|
||||||
|
TOKEN_VIEW: 'Token View',
|
||||||
|
},
|
||||||
|
TRANSACTION: {
|
||||||
|
USER: 'user',
|
||||||
|
DAPP: 'dapp',
|
||||||
|
},
|
||||||
|
TOKEN: {
|
||||||
|
CUSTOM: 'custom',
|
||||||
|
DETECTED: 'detected',
|
||||||
|
DAPP: 'dapp',
|
||||||
|
LIST: 'list',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LOCATION: {
|
||||||
|
TOKEN_DETECTION: 'token_detection',
|
||||||
|
TOKEN_MENU: 'token_menu',
|
||||||
|
TOKEN_DETAILS: 'token_details',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
@ -7,11 +7,32 @@ import Box from '../../../ui/box/box';
|
|||||||
import Button from '../../../ui/button';
|
import Button from '../../../ui/button';
|
||||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||||
import { getDetectedTokensInCurrentNetwork } from '../../../../selectors';
|
import { getDetectedTokensInCurrentNetwork } from '../../../../selectors';
|
||||||
|
import { MetaMetricsContext } from '../../../../contexts/metametrics';
|
||||||
|
import {
|
||||||
|
EVENT,
|
||||||
|
EVENT_NAMES,
|
||||||
|
} from '../../../../../shared/constants/metametrics';
|
||||||
|
|
||||||
const DetectedTokensLink = ({ className = '', setShowDetectedTokens }) => {
|
const DetectedTokensLink = ({ className = '', setShowDetectedTokens }) => {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork);
|
const trackEvent = useContext(MetaMetricsContext);
|
||||||
|
|
||||||
|
const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork);
|
||||||
|
const detectedTokensDetails = detectedTokens.map(
|
||||||
|
({ address, symbol }) => `${symbol} - ${address}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
setShowDetectedTokens(true);
|
||||||
|
trackEvent({
|
||||||
|
event: EVENT_NAMES.TOKEN_IMPORT_CLICKED,
|
||||||
|
category: EVENT.CATEGORIES.WALLET,
|
||||||
|
properties: {
|
||||||
|
source: EVENT.SOURCE.TOKEN.DETECTED,
|
||||||
|
tokens: detectedTokensDetails,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
className={classNames('detected-tokens-link', className)}
|
className={classNames('detected-tokens-link', className)}
|
||||||
@ -20,7 +41,7 @@ const DetectedTokensLink = ({ className = '', setShowDetectedTokens }) => {
|
|||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
className="detected-tokens-link__link"
|
className="detected-tokens-link__link"
|
||||||
onClick={() => setShowDetectedTokens(true)}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{t('numberOfNewTokensDetected', [detectedTokens.length])}
|
{t('numberOfNewTokensDetected', [detectedTokens.length])}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import testData from '../../../../../.storybook/test-data';
|
||||||
|
import configureStore from '../../../../store/store';
|
||||||
|
import DetectedTokensLink from './detected-tokens-link';
|
||||||
|
|
||||||
|
const store = configureStore(testData);
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/App/AssetList/DetectedTokensLink',
|
||||||
|
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
|
||||||
|
id: __filename,
|
||||||
|
argTypes: {
|
||||||
|
setShowDetectedTokens: { control: 'func' },
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
setShowDetectedTokens: 'setShowDetectedTokensSpy',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const Template = (args) => {
|
||||||
|
return <DetectedTokensLink {...args} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DefaultStory = Template.bind({});
|
||||||
|
|
||||||
|
DefaultStory.storyName = 'Default';
|
@ -0,0 +1,32 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import {
|
||||||
|
renderWithProvider,
|
||||||
|
screen,
|
||||||
|
fireEvent,
|
||||||
|
} from '../../../../../test/jest';
|
||||||
|
import configureStore from '../../../../store/store';
|
||||||
|
import testData from '../../../../../.storybook/test-data';
|
||||||
|
|
||||||
|
import DetectedTokensLink from './detected-tokens-link';
|
||||||
|
|
||||||
|
describe('DetectedTokensLink', () => {
|
||||||
|
let setShowDetectedTokensSpy;
|
||||||
|
const args = {};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
setShowDetectedTokensSpy = jest.fn();
|
||||||
|
args.setShowDetectedTokens = setShowDetectedTokensSpy;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render number of tokens detected link', () => {
|
||||||
|
const store = configureStore(testData);
|
||||||
|
renderWithProvider(<DetectedTokensLink {...args} />, store);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText('3 new tokens found in this account'),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('3 new tokens found in this account'));
|
||||||
|
expect(setShowDetectedTokensSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
@ -1,8 +1,13 @@
|
|||||||
import React from 'react';
|
import React, { useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||||
|
import { MetaMetricsContext } from '../../../../contexts/metametrics';
|
||||||
|
import {
|
||||||
|
EVENT,
|
||||||
|
EVENT_NAMES,
|
||||||
|
} from '../../../../../shared/constants/metametrics';
|
||||||
import { getDetectedTokensInCurrentNetwork } from '../../../../selectors';
|
import { getDetectedTokensInCurrentNetwork } from '../../../../selectors';
|
||||||
|
|
||||||
import Popover from '../../../ui/popover';
|
import Popover from '../../../ui/popover';
|
||||||
@ -19,6 +24,7 @@ const DetectedTokenSelectionPopover = ({
|
|||||||
sortingBasedOnTokenSelection,
|
sortingBasedOnTokenSelection,
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
|
const trackEvent = useContext(MetaMetricsContext);
|
||||||
|
|
||||||
const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork);
|
const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork);
|
||||||
const { selected: selectedTokens = [] } = sortingBasedOnTokenSelection(
|
const { selected: selectedTokens = [] } = sortingBasedOnTokenSelection(
|
||||||
@ -31,6 +37,17 @@ const DetectedTokenSelectionPopover = ({
|
|||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
setShowDetectedTokens(false);
|
setShowDetectedTokens(false);
|
||||||
|
const eventTokensDetails = detectedTokens.map(
|
||||||
|
({ address, symbol }) => `${symbol} - ${address}`,
|
||||||
|
);
|
||||||
|
trackEvent({
|
||||||
|
event: EVENT_NAMES.TOKEN_IMPORT_CANCELED,
|
||||||
|
category: EVENT.CATEGORIES.WALLET,
|
||||||
|
properties: {
|
||||||
|
source: EVENT.SOURCE.TOKEN.DETECTED,
|
||||||
|
tokens: eventTokensDetails,
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const footer = (
|
const footer = (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { chain } from 'lodash';
|
import { chain } from 'lodash';
|
||||||
@ -9,7 +9,11 @@ import {
|
|||||||
setNewTokensImported,
|
setNewTokensImported,
|
||||||
} from '../../../store/actions';
|
} from '../../../store/actions';
|
||||||
import { getDetectedTokensInCurrentNetwork } from '../../../selectors';
|
import { getDetectedTokensInCurrentNetwork } from '../../../selectors';
|
||||||
|
import { MetaMetricsContext } from '../../../contexts/metametrics';
|
||||||
|
|
||||||
|
import { TOKEN_STANDARDS } from '../../../helpers/constants/common';
|
||||||
|
import { ASSET_TYPES } from '../../../../shared/constants/transaction';
|
||||||
|
import { EVENT, EVENT_NAMES } from '../../../../shared/constants/metametrics';
|
||||||
import DetectedTokenSelectionPopover from './detected-token-selection-popover/detected-token-selection-popover';
|
import DetectedTokenSelectionPopover from './detected-token-selection-popover/detected-token-selection-popover';
|
||||||
import DetectedTokenIgnoredPopover from './detected-token-ignored-popover/detected-token-ignored-popover';
|
import DetectedTokenIgnoredPopover from './detected-token-ignored-popover/detected-token-ignored-popover';
|
||||||
|
|
||||||
@ -26,8 +30,10 @@ const sortingBasedOnTokenSelection = (tokensDetected) => {
|
|||||||
.value()
|
.value()
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DetectedToken = ({ setShowDetectedTokens }) => {
|
const DetectedToken = ({ setShowDetectedTokens }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const trackEvent = useContext(MetaMetricsContext);
|
||||||
|
|
||||||
const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork);
|
const detectedTokens = useSelector(getDetectedTokensInCurrentNetwork);
|
||||||
|
|
||||||
@ -42,21 +48,49 @@ const DetectedToken = ({ setShowDetectedTokens }) => {
|
|||||||
setShowDetectedTokenIgnoredPopover,
|
setShowDetectedTokenIgnoredPopover,
|
||||||
] = useState(false);
|
] = useState(false);
|
||||||
|
|
||||||
|
const importSelectedTokens = async (selectedTokens) => {
|
||||||
|
selectedTokens.forEach((importedToken) => {
|
||||||
|
trackEvent({
|
||||||
|
event: EVENT_NAMES.TOKEN_ADDED,
|
||||||
|
category: EVENT.CATEGORIES.WALLET,
|
||||||
|
sensitiveProperties: {
|
||||||
|
token_symbol: importedToken.symbol,
|
||||||
|
token_contract_address: importedToken.address,
|
||||||
|
token_decimal_precision: importedToken.decimals,
|
||||||
|
source: EVENT.SOURCE.TOKEN.DETECTED,
|
||||||
|
token_standard: TOKEN_STANDARDS.ERC20,
|
||||||
|
asset_type: ASSET_TYPES.TOKEN,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await dispatch(importTokens(selectedTokens));
|
||||||
|
const tokenSymbols = selectedTokens.map(({ symbol }) => symbol);
|
||||||
|
dispatch(setNewTokensImported(tokenSymbols.join(', ')));
|
||||||
|
};
|
||||||
|
|
||||||
const handleClearTokensSelection = async () => {
|
const handleClearTokensSelection = async () => {
|
||||||
// create a lodash chain on this object
|
|
||||||
const {
|
const {
|
||||||
selected: selectedTokens,
|
selected: selectedTokens = [],
|
||||||
deselected: deSelectedTokens,
|
deselected: deSelectedTokens = [],
|
||||||
} = sortingBasedOnTokenSelection(tokensListDetected);
|
} = sortingBasedOnTokenSelection(tokensListDetected);
|
||||||
|
|
||||||
if (deSelectedTokens.length < detectedTokens.length) {
|
if (deSelectedTokens.length < detectedTokens.length) {
|
||||||
await dispatch(ignoreTokens(deSelectedTokens));
|
await importSelectedTokens(selectedTokens);
|
||||||
await dispatch(importTokens(selectedTokens));
|
|
||||||
const tokenSymbols = selectedTokens.map(({ symbol }) => symbol);
|
|
||||||
dispatch(setNewTokensImported(tokenSymbols.join(', ')));
|
|
||||||
} else {
|
|
||||||
await dispatch(ignoreTokens(deSelectedTokens));
|
|
||||||
}
|
}
|
||||||
|
const tokensDetailsList = deSelectedTokens.map(
|
||||||
|
({ symbol, address }) => `${symbol} - ${address}`,
|
||||||
|
);
|
||||||
|
trackEvent({
|
||||||
|
event: EVENT_NAMES.TOKEN_HIDDEN,
|
||||||
|
category: EVENT.CATEGORIES.WALLET,
|
||||||
|
sensitiveProperties: {
|
||||||
|
tokens: tokensDetailsList,
|
||||||
|
location: EVENT.LOCATION.TOKEN_DETECTION,
|
||||||
|
token_standard: TOKEN_STANDARDS.ERC20,
|
||||||
|
asset_type: ASSET_TYPES.TOKEN,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await dispatch(ignoreTokens(deSelectedTokens));
|
||||||
setShowDetectedTokens(false);
|
setShowDetectedTokens(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -71,17 +105,14 @@ const DetectedToken = ({ setShowDetectedTokens }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onImport = async () => {
|
const onImport = async () => {
|
||||||
// create a lodash chain on this object
|
const { selected: selectedTokens = [] } = sortingBasedOnTokenSelection(
|
||||||
const { selected: selectedTokens } = sortingBasedOnTokenSelection(
|
|
||||||
tokensListDetected,
|
tokensListDetected,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selectedTokens.length < detectedTokens.length) {
|
if (selectedTokens.length < detectedTokens.length) {
|
||||||
setShowDetectedTokenIgnoredPopover(true);
|
setShowDetectedTokenIgnoredPopover(true);
|
||||||
} else {
|
} else {
|
||||||
const tokenSymbols = selectedTokens.map(({ symbol }) => symbol);
|
await importSelectedTokens(selectedTokens);
|
||||||
await dispatch(importTokens(selectedTokens));
|
|
||||||
dispatch(setNewTokensImported(tokenSymbols.join(', ')));
|
|
||||||
setShowDetectedTokens(false);
|
setShowDetectedTokens(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -136,7 +136,7 @@ const EthOverview = ({ className }) => {
|
|||||||
event: 'Swaps Opened',
|
event: 'Swaps Opened',
|
||||||
category: EVENT.CATEGORIES.SWAPS,
|
category: EVENT.CATEGORIES.SWAPS,
|
||||||
properties: {
|
properties: {
|
||||||
source: 'Main View',
|
source: EVENT.SOURCE.SWAPS.MAIN_VIEW,
|
||||||
active_currency: 'ETH',
|
active_currency: 'ETH',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -120,7 +120,7 @@ const TokenOverview = ({ className, token }) => {
|
|||||||
event: 'Swaps Opened',
|
event: 'Swaps Opened',
|
||||||
category: EVENT.CATEGORIES.SWAPS,
|
category: EVENT.CATEGORIES.SWAPS,
|
||||||
properties: {
|
properties: {
|
||||||
source: 'Token View',
|
source: EVENT.SOURCE.SWAPS.TOKEN_VIEW,
|
||||||
active_currency: token.symbol,
|
active_currency: token.symbol,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -27,6 +27,7 @@ import { getCollectiblesDetectionNoticeDismissed } from '../../ducks/metamask/me
|
|||||||
import CollectiblesDetectionNotice from '../../components/app/collectibles-detection-notice';
|
import CollectiblesDetectionNotice from '../../components/app/collectibles-detection-notice';
|
||||||
import { MetaMetricsContext } from '../../contexts/metametrics';
|
import { MetaMetricsContext } from '../../contexts/metametrics';
|
||||||
import { ASSET_TYPES } from '../../../shared/constants/transaction';
|
import { ASSET_TYPES } from '../../../shared/constants/transaction';
|
||||||
|
import { EVENT, EVENT_NAMES } from '../../../shared/constants/metametrics';
|
||||||
|
|
||||||
export default function AddCollectible() {
|
export default function AddCollectible() {
|
||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
@ -77,7 +78,7 @@ export default function AddCollectible() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
trackEvent({
|
trackEvent({
|
||||||
event: 'Token Added',
|
event: EVENT_NAMES.TOKEN_ADDED,
|
||||||
category: 'Wallet',
|
category: 'Wallet',
|
||||||
sensitiveProperties: {
|
sensitiveProperties: {
|
||||||
token_contract_address: address,
|
token_contract_address: address,
|
||||||
@ -85,7 +86,7 @@ export default function AddCollectible() {
|
|||||||
tokenId: tokenId.toString(),
|
tokenId: tokenId.toString(),
|
||||||
asset_type: ASSET_TYPES.COLLECTIBLE,
|
asset_type: ASSET_TYPES.COLLECTIBLE,
|
||||||
token_standard: tokenDetails?.standard,
|
token_standard: tokenDetails?.standard,
|
||||||
source: 'custom',
|
source: EVENT.SOURCE.TOKEN.CUSTOM,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
|
|||||||
import { getSuggestedAssets } from '../../selectors';
|
import { getSuggestedAssets } from '../../selectors';
|
||||||
import { rejectWatchAsset, acceptWatchAsset } from '../../store/actions';
|
import { rejectWatchAsset, acceptWatchAsset } from '../../store/actions';
|
||||||
import { TOKEN_STANDARDS } from '../../helpers/constants/common';
|
import { TOKEN_STANDARDS } from '../../helpers/constants/common';
|
||||||
import { EVENT } from '../../../shared/constants/metametrics';
|
import { EVENT, EVENT_NAMES } from '../../../shared/constants/metametrics';
|
||||||
import { ASSET_TYPES } from '../../../shared/constants/transaction';
|
import { ASSET_TYPES } from '../../../shared/constants/transaction';
|
||||||
|
|
||||||
function getTokenName(name, symbol) {
|
function getTokenName(name, symbol) {
|
||||||
@ -115,14 +115,14 @@ const ConfirmAddSuggestedToken = () => {
|
|||||||
await dispatch(acceptWatchAsset(id));
|
await dispatch(acceptWatchAsset(id));
|
||||||
|
|
||||||
trackEvent({
|
trackEvent({
|
||||||
event: 'Token Added',
|
event: EVENT_NAMES.TOKEN_ADDED,
|
||||||
category: EVENT.CATEGORIES.WALLET,
|
category: EVENT.CATEGORIES.WALLET,
|
||||||
sensitiveProperties: {
|
sensitiveProperties: {
|
||||||
token_symbol: asset.symbol,
|
token_symbol: asset.symbol,
|
||||||
token_contract_address: asset.address,
|
token_contract_address: asset.address,
|
||||||
token_decimal_precision: asset.decimals,
|
token_decimal_precision: asset.decimals,
|
||||||
unlisted: asset.unlisted,
|
unlisted: asset.unlisted,
|
||||||
source: 'dapp',
|
source: EVENT.SOURCE.TOKEN.DAPP,
|
||||||
token_standard: TOKEN_STANDARDS.ERC20,
|
token_standard: TOKEN_STANDARDS.ERC20,
|
||||||
asset_type: ASSET_TYPES.TOKEN,
|
asset_type: ASSET_TYPES.TOKEN,
|
||||||
},
|
},
|
||||||
|
@ -13,7 +13,8 @@ import { MetaMetricsContext } from '../../contexts/metametrics';
|
|||||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||||
import { getPendingTokens } from '../../ducks/metamask/metamask';
|
import { getPendingTokens } from '../../ducks/metamask/metamask';
|
||||||
import { addTokens, clearPendingTokens } from '../../store/actions';
|
import { addTokens, clearPendingTokens } from '../../store/actions';
|
||||||
import { EVENT } from '../../../shared/constants/metametrics';
|
import { TOKEN_STANDARDS } from '../../helpers/constants/common';
|
||||||
|
import { EVENT, EVENT_NAMES } from '../../../shared/constants/metametrics';
|
||||||
import { ASSET_TYPES } from '../../../shared/constants/transaction';
|
import { ASSET_TYPES } from '../../../shared/constants/transaction';
|
||||||
|
|
||||||
const getTokenName = (name, symbol) => {
|
const getTokenName = (name, symbol) => {
|
||||||
@ -37,15 +38,17 @@ const ConfirmImportToken = () => {
|
|||||||
|
|
||||||
addedTokenValues.forEach((pendingToken) => {
|
addedTokenValues.forEach((pendingToken) => {
|
||||||
trackEvent({
|
trackEvent({
|
||||||
event: 'Token Added',
|
event: EVENT_NAMES.TOKEN_ADDED,
|
||||||
category: EVENT.CATEGORIES.WALLET,
|
category: EVENT.CATEGORIES.WALLET,
|
||||||
sensitiveProperties: {
|
sensitiveProperties: {
|
||||||
token_symbol: pendingToken.symbol,
|
token_symbol: pendingToken.symbol,
|
||||||
token_contract_address: pendingToken.address,
|
token_contract_address: pendingToken.address,
|
||||||
token_decimal_precision: pendingToken.decimals,
|
token_decimal_precision: pendingToken.decimals,
|
||||||
unlisted: pendingToken.unlisted,
|
unlisted: pendingToken.unlisted,
|
||||||
source: pendingToken.isCustom ? 'custom' : 'list',
|
source: pendingToken.isCustom
|
||||||
token_standard: pendingToken.standard,
|
? EVENT.SOURCE.TOKEN.CUSTOM
|
||||||
|
: EVENT.SOURCE.TOKEN.LIST,
|
||||||
|
token_standard: TOKEN_STANDARDS.ERC20,
|
||||||
asset_type: ASSET_TYPES.TOKEN,
|
asset_type: ASSET_TYPES.TOKEN,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user