Skip to content

Commit

Permalink
Merge branch 'salim/token-autodetection-multi-chain' into salim/multi…
Browse files Browse the repository at this point in the history
…chain-token-detection-final
  • Loading branch information
salimtb committed Nov 20, 2024
2 parents 675780e + ba2c169 commit dce2001
Show file tree
Hide file tree
Showing 43 changed files with 1,104 additions and 174 deletions.
7 changes: 7 additions & 0 deletions .storybook/test-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,13 @@ const state = {
decimals: 18,
},
],
tokenBalances: {
'0x64a845a5b02460acf8a3d84503b0d68d028b4bb4': {
'0x1': {
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48': '0x25e4bc',
},
},
},
allDetectedTokens: {
'0xaa36a7': {
'0x9d0ba4ddac06032527b140912ec808ab9451b788': [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
diff --git a/dist/assetsUtil.cjs b/dist/assetsUtil.cjs
index 48571b8c1b78e94d88e1837e986b5f8735ac651b..61246f51500c8cab48f18296a73629fb73454caa 100644
--- a/dist/assetsUtil.cjs
+++ b/dist/assetsUtil.cjs
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } }
exports.fetchTokenContractExchangeRates = exports.reduceInBatchesSerially = exports.divideIntoBatches = exports.ethersBigNumberToBN = exports.addUrlProtocolPrefix = exports.getFormattedIpfsUrl = exports.getIpfsCIDv1AndPath = exports.removeIpfsProtocolPrefix = exports.isTokenListSupportedForNetwork = exports.isTokenDetectionSupportedForNetwork = exports.SupportedStakedBalanceNetworks = exports.SupportedTokenDetectionNetworks = exports.formatIconUrlWithProxy = exports.formatAggregatorNames = exports.hasNewCollectionFields = exports.compareNftMetadata = exports.TOKEN_PRICES_BATCH_SIZE = void 0;
const controller_utils_1 = require("@metamask/controller-utils");
const utils_1 = require("@metamask/utils");
@@ -233,7 +234,7 @@ async function getIpfsCIDv1AndPath(ipfsUrl) {
const index = url.indexOf('/');
const cid = index !== -1 ? url.substring(0, index) : url;
const path = index !== -1 ? url.substring(index) : undefined;
- const { CID } = await import("multiformats");
+ const { CID } = _interopRequireWildcard(require("multiformats"));
// We want to ensure that the CID is v1 (https://docs.ipfs.io/concepts/content-addressing/#identifier-formats)
// because most cid v0s appear to be incompatible with IPFS subdomains
return {
diff --git a/dist/token-prices-service/codefi-v2.mjs b/dist/token-prices-service/codefi-v2.mjs
index e7eaad2cfa8b233c4fd42a51f745233a1cc5c387..bf8ec7819f678c2f185d6a85d7e3ea81f055a309 100644
--- a/dist/token-prices-service/codefi-v2.mjs
+++ b/dist/token-prices-service/codefi-v2.mjs
@@ -12,8 +12,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
var _CodefiTokenPricesServiceV2_tokenPricePolicy;
import { handleFetch } from "@metamask/controller-utils";
import { hexToNumber } from "@metamask/utils";
-import $cockatiel from "cockatiel";
-const { circuitBreaker, ConsecutiveBreaker, ExponentialBackoff, handleAll, retry, wrap, CircuitState } = $cockatiel;
+import { circuitBreaker, ConsecutiveBreaker, ExponentialBackoff, handleAll, retry, wrap, CircuitState } from "cockatiel"
/**
* The list of currencies that can be supplied as the `vsCurrency` parameter to
* the `/spot-prices` endpoint, in lowercase form.
9 changes: 9 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/scripts/lib/setupSentry.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ function getTracesSampleRate(sentryTarget) {
return 1.0;
}

return 0.02;
return 0.01;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@
"@metamask/address-book-controller": "^6.0.0",
"@metamask/announcement-controller": "^7.0.0",
"@metamask/approval-controller": "^7.0.0",
"@metamask/assets-controllers": "patch:@metamask/assets-controllers@patch%3A@metamask/assets-controllers@npm%253A44.0.0%23~/.yarn/patches/@metamask-assets-controllers-npm-44.0.0-c223d56176.patch%3A%3Aversion=44.0.0&hash=5a94c2#~/.yarn/patches/@metamask-assets-controllers-patch-9e00573eb4.patch",
"@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A44.1.0#~/.yarn/patches/@metamask-assets-controllers-npm-44.1.0-012aa448d8.patch",
"@metamask/base-controller": "^7.0.0",
"@metamask/bitcoin-wallet-snap": "^0.8.2",
"@metamask/browser-passworder": "^4.3.0",
Expand Down
27 changes: 27 additions & 0 deletions test/data/mock-data.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 26 additions & 8 deletions ui/components/app/assets/asset-list/asset-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import TokenList from '../token-list';
import { PRIMARY } from '../../../../helpers/constants/common';
import { useUserPreferencedCurrency } from '../../../../hooks/useUserPreferencedCurrency';
import {
getAllDetectedTokensForSelectedAddress,
getDetectedTokensInCurrentNetwork,
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
getPreferences,
getSelectedAccount,
} from '../../../../selectors';
import {
Expand Down Expand Up @@ -74,6 +76,8 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => {
const isTokenDetectionInactiveOnNonMainnetSupportedNetwork = useSelector(
getIstokenDetectionInactiveOnNonMainnetSupportedNetwork,
);
const { tokenNetworkFilter } = useSelector(getPreferences);
const allNetworksFilterShown = Object.keys(tokenNetworkFilter ?? {}).length;

const [showFundingMethodModal, setShowFundingMethodModal] = useState(false);
const [showReceiveModal, setShowReceiveModal] = useState(false);
Expand All @@ -98,16 +102,30 @@ const AssetList = ({ onClickAsset, showTokensLinks }: AssetListProps) => {
// for EVM assets
const shouldShowTokensLinks = showTokensLinks ?? isEvm;

const detectedTokensMultichain = useSelector(
getAllDetectedTokensForSelectedAddress,
);

const totalTokens =
process.env.PORTFOLIO_VIEW && !allNetworksFilterShown
? (Object.values(detectedTokensMultichain).reduce(
// @ts-expect-error TS18046: 'tokenArray' is of type 'unknown'
(count, tokenArray) => count + tokenArray.length,
0,
) as number)
: detectedTokens.length;

return (
<>
{detectedTokens.length > 0 &&
!isTokenDetectionInactiveOnNonMainnetSupportedNetwork && (
<DetectedTokensBanner
className=""
actionButtonOnClick={() => setShowDetectedTokens(true)}
margin={4}
/>
)}
{totalTokens &&
totalTokens > 0 &&
!isTokenDetectionInactiveOnNonMainnetSupportedNetwork ? (
<DetectedTokensBanner
className=""
actionButtonOnClick={() => setShowDetectedTokens(true)}
margin={4}
/>
) : null}
<AssetListControlBar showTokensLinks={showTokensLinks} />
<TokenList
onTokenClick={(chainId: string, tokenAddress: string) => {
Expand Down
61 changes: 54 additions & 7 deletions ui/components/app/contact-list/contact-list.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { sortBy } from 'lodash';
import Button from '../../ui/button';
import { BannerAlert, BannerAlertSeverity } from '../../component-library';
import RecipientGroup from './recipient-group/recipient-group.component';
import { hasDuplicateContacts, buildDuplicateContactMap } from './utils';

export default class ContactList extends PureComponent {
static propTypes = {
addressBook: PropTypes.array,
internalAccounts: PropTypes.array,
searchForContacts: PropTypes.func,
searchForRecents: PropTypes.func,
searchForMyAccounts: PropTypes.func,
Expand All @@ -22,6 +26,19 @@ export default class ContactList extends PureComponent {
isShowingAllRecent: false,
};

renderDuplicateContactWarning() {
const { t } = this.context;

return (
<div className="send__select-recipient-wrapper__list__duplicate-contact-banner">
<BannerAlert
severity={BannerAlertSeverity.Warning}
description={t('duplicateContactWarning')}
/>
</div>
);
}

renderRecents() {
const { t } = this.context;
const { isShowingAllRecent } = this.state;
Expand All @@ -45,15 +62,40 @@ export default class ContactList extends PureComponent {
}

renderAddressBook() {
const unsortedContactsByLetter = this.props
.searchForContacts()
.reduce((obj, contact) => {
const {
addressBook,
internalAccounts,
searchForContacts,
selectRecipient,
selectedAddress,
} = this.props;

const duplicateContactMap = buildDuplicateContactMap(
addressBook,
internalAccounts,
);

const unsortedContactsByLetter = searchForContacts().reduce(
(obj, contact) => {
const firstLetter = contact.name[0].toUpperCase();

const isDuplicate =
(duplicateContactMap.get(contact.name.trim().toLowerCase()) ?? [])
.length > 1;

return {
...obj,
[firstLetter]: [...(obj[firstLetter] || []), contact],
[firstLetter]: [
...(obj[firstLetter] || []),
{
...contact,
isDuplicate,
},
],
};
}, {});
},
{},
);

const letters = Object.keys(unsortedContactsByLetter).sort();

Expand All @@ -71,8 +113,8 @@ export default class ContactList extends PureComponent {
key={`${letter}-contact-group`}
label={letter}
items={groupItems}
onSelect={this.props.selectRecipient}
selectedAddress={this.props.selectedAddress}
onSelect={selectRecipient}
selectedAddress={selectedAddress}
/>
));
}
Expand All @@ -95,11 +137,16 @@ export default class ContactList extends PureComponent {
searchForRecents,
searchForContacts,
searchForMyAccounts,
addressBook,
internalAccounts,
} = this.props;

return (
<div className="send__select-recipient-wrapper__list">
{children || null}
{hasDuplicateContacts(addressBook, internalAccounts)
? this.renderDuplicateContactWarning()
: null}
{searchForRecents ? this.renderRecents() : null}
{searchForContacts ? this.renderAddressBook() : null}
{searchForMyAccounts ? this.renderMyAccounts() : null}
Expand Down
46 changes: 46 additions & 0 deletions ui/components/app/contact-list/contact-list.test.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,57 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { renderWithProvider } from '../../../../test/jest/rendering';
import { MOCK_ADDRESS_BOOK } from '../../../../test/data/mock-data';
import { createMockInternalAccount } from '../../../../test/jest/mocks';
import ContactList from '.';

describe('Contact List', () => {
const store = configureMockStore([])({
metamask: {},
});

const mockInternalAccounts = [createMockInternalAccount()];

it('displays the warning banner when multiple contacts have the same name', () => {
const mockAddressBook = [...MOCK_ADDRESS_BOOK, MOCK_ADDRESS_BOOK[0]]; // Adding duplicate contact

const { getByText } = renderWithProvider(
<ContactList
addressBook={mockAddressBook}
internalAccounts={mockInternalAccounts}
/>,
store,
);

const duplicateContactBanner = getByText('You have duplicate contacts');

expect(duplicateContactBanner).toBeVisible();
});

it('displays the warning banner when contact has same name as an existing account', () => {
const mockContactWithAccountName = {
address: '0x2f318C334780961FB129D2a6c30D0763d9a5C970',
chainId: '0x1',
isEns: false,
memo: '',
name: mockInternalAccounts[0].metadata.name,
};

const mockAddressBook = [...MOCK_ADDRESS_BOOK, mockContactWithAccountName];

const { getByText } = renderWithProvider(
<ContactList
addressBook={mockAddressBook}
internalAccounts={mockInternalAccounts}
/>,
store,
);

const duplicateContactBanner = getByText('You have duplicate contacts');

expect(duplicateContactBanner).toBeVisible();
});

describe('given searchForContacts', () => {
const selectRecipient = () => null;
const selectedAddress = null;
Expand Down Expand Up @@ -37,6 +81,8 @@ describe('Contact List', () => {
searchForContacts={() => contacts}
selectRecipient={selectRecipient}
selectedAddress={selectedAddress}
addressBook={MOCK_ADDRESS_BOOK}
internalAccounts={mockInternalAccounts}
/>,
store,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ export default function RecipientGroup({ items, onSelect }) {
return null;
}

return items.map(({ address, name }) => (
return items.map(({ address, name, isDuplicate }) => (
<AddressListItem
address={address}
label={name}
onClick={() => onSelect(address, name)}
key={address}
isDuplicate={isDuplicate}
/>
));
}
Expand Down
Loading

0 comments on commit dce2001

Please sign in to comment.