diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json
index c33171c0e..bc842087e 100644
--- a/app/_locales/el/messages.json
+++ b/app/_locales/el/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "Προσθήκη σημειώματος"
},
- "addNFT": {
- "message": "Προσθήκη NFT"
- },
"addNetwork": {
"message": "Προσθήκη Δικτύου"
},
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 206aababa..b70a85694 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "Add memo"
},
- "addNFT": {
- "message": "Add NFT"
- },
"addNetwork": {
"message": "Add Network"
},
@@ -458,6 +455,10 @@
"close": {
"message": "Close"
},
+ "collectibleAddressError": {
+ "message": "This token is an NFT. Add on the $1",
+ "description": "$1 is a clickable link with text defined by the 'importNFTPage' key"
+ },
"confirm": {
"message": "Confirm"
},
@@ -1402,6 +1403,12 @@
"importMyWallet": {
"message": "Import My Wallet"
},
+ "importNFT": {
+ "message": "Import NFT"
+ },
+ "importNFTPage": {
+ "message": "Import NFT page"
+ },
"importNFTs": {
"message": "Import NFTs"
},
diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json
index a01a75db8..30a055b87 100644
--- a/app/_locales/fr/messages.json
+++ b/app/_locales/fr/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "Ajouter un mémo"
},
- "addNFT": {
- "message": "Ajouter un NFT"
- },
"addNetwork": {
"message": "Ajouter un réseau"
},
diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json
index 580fbdd35..b6cf3ed1a 100644
--- a/app/_locales/hi/messages.json
+++ b/app/_locales/hi/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "मेमो जोड़ें"
},
- "addNFT": {
- "message": "NFT जोड़ें"
- },
"addNetwork": {
"message": "नेटवर्क जोड़ें"
},
diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json
index 18ac64d24..32a1485df 100644
--- a/app/_locales/id/messages.json
+++ b/app/_locales/id/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "Tambahkan memo"
},
- "addNFT": {
- "message": "Tambahkan NFT"
- },
"addNetwork": {
"message": "Tambahkan Jaringan"
},
diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json
index 381b4c943..c6d4be056 100644
--- a/app/_locales/ja/messages.json
+++ b/app/_locales/ja/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "メモを追加"
},
- "addNFT": {
- "message": "NFTを追加"
- },
"addNetwork": {
"message": "ネットワークの追加"
},
diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json
index d2ab73621..1645eff00 100644
--- a/app/_locales/ko/messages.json
+++ b/app/_locales/ko/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "메모 추가"
},
- "addNFT": {
- "message": "NFT 추가"
- },
"addNetwork": {
"message": "네트워크 추가"
},
diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json
index e438e24b0..2623ae63d 100644
--- a/app/_locales/ru/messages.json
+++ b/app/_locales/ru/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "Добавить примечание"
},
- "addNFT": {
- "message": "Добавить NFT"
- },
"addNetwork": {
"message": "Добавить сеть"
},
diff --git a/app/_locales/tl/messages.json b/app/_locales/tl/messages.json
index 5f9eb4d03..f90ef7fe6 100644
--- a/app/_locales/tl/messages.json
+++ b/app/_locales/tl/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "Magdagdag ng memo"
},
- "addNFT": {
- "message": "Magdagdag ng NFT"
- },
"addNetwork": {
"message": "Magdagdag ng Network"
},
diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json
index 2fc5cba1c..bab183e51 100644
--- a/app/_locales/tr/messages.json
+++ b/app/_locales/tr/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "Not ekleyin"
},
- "addNFT": {
- "message": "NFT ekleyin"
- },
"addNetwork": {
"message": "Ağ ekleyin"
},
diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json
index 060c5e485..7f2ad991a 100644
--- a/app/_locales/vi/messages.json
+++ b/app/_locales/vi/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "Thêm bản ghi nhớ"
},
- "addNFT": {
- "message": "Thêm NFT"
- },
"addNetwork": {
"message": "Thêm mạng"
},
diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json
index d1fb4e637..b90d3be9c 100644
--- a/app/_locales/zh_CN/messages.json
+++ b/app/_locales/zh_CN/messages.json
@@ -140,9 +140,6 @@
"addMemo": {
"message": "添加备忘录"
},
- "addNFT": {
- "message": "添加NFT"
- },
"addNetwork": {
"message": "添加网络"
},
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 205472581..bcf85dd52 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -220,22 +220,22 @@ export default class MetamaskController extends EventEmitter {
onNetworkStateChange: this.networkController.store.subscribe.bind(
this.networkController.store,
),
- getAssetName: this.assetsContractController.getAssetName.bind(
+ getERC721AssetName: this.assetsContractController.getERC721AssetName.bind(
this.assetsContractController,
),
- getAssetSymbol: this.assetsContractController.getAssetSymbol.bind(
+ getERC721AssetSymbol: this.assetsContractController.getERC721AssetSymbol.bind(
this.assetsContractController,
),
- getCollectibleTokenURI: this.assetsContractController.getCollectibleTokenURI.bind(
+ getERC721TokenURI: this.assetsContractController.getERC721TokenURI.bind(
this.assetsContractController,
),
- getOwnerOf: this.assetsContractController.getOwnerOf.bind(
+ getERC721OwnerOf: this.assetsContractController.getERC721OwnerOf.bind(
this.assetsContractController,
),
- balanceOfERC1155Collectible: this.assetsContractController.balanceOfERC1155Collectible.bind(
+ getERC1155BalanceOf: this.assetsContractController.getERC1155BalanceOf.bind(
this.assetsContractController,
),
- uriERC1155Collectible: this.assetsContractController.uriERC1155Collectible.bind(
+ getERC1155TokenURI: this.assetsContractController.getERC1155TokenURI.bind(
this.assetsContractController,
),
},
@@ -1040,6 +1040,7 @@ export default class MetamaskController extends EventEmitter {
appStateController,
collectiblesController,
collectibleDetectionController,
+ assetsContractController,
currencyRateController,
detectTokensController,
ensController,
@@ -1192,6 +1193,11 @@ export default class MetamaskController extends EventEmitter {
preferencesController,
),
+ // AssetsContractController
+ getTokenStandardAndDetails: assetsContractController.getTokenStandardAndDetails.bind(
+ assetsContractController,
+ ),
+
// CollectiblesController
addCollectible: collectiblesController.addCollectible.bind(
collectiblesController,
diff --git a/package.json b/package.json
index e9531d171..acaf95868 100644
--- a/package.json
+++ b/package.json
@@ -107,7 +107,7 @@
"@keystonehq/metamask-airgapped-keyring": "0.2.1",
"@material-ui/core": "^4.11.0",
"@metamask/contract-metadata": "^1.31.0",
- "@metamask/controllers": "^24.0.0",
+ "@metamask/controllers": "^25.0.0",
"@metamask/eth-ledger-bridge-keyring": "^0.10.0",
"@metamask/eth-token-tracker": "^3.0.1",
"@metamask/etherscan-link": "^2.1.0",
diff --git a/ui/components/ui/page-container/index.scss b/ui/components/ui/page-container/index.scss
index a249bb912..45f5f8854 100644
--- a/ui/components/ui/page-container/index.scss
+++ b/ui/components/ui/page-container/index.scss
@@ -28,6 +28,7 @@
right: 16px;
cursor: pointer;
overflow: hidden;
+ background-color: transparent;
&::after {
content: '\00D7';
diff --git a/ui/components/ui/page-container/page-container-header/page-container-header.component.js b/ui/components/ui/page-container/page-container-header/page-container-header.component.js
index 78f71b65a..9b4787e0e 100644
--- a/ui/components/ui/page-container/page-container-header/page-container-header.component.js
+++ b/ui/components/ui/page-container/page-container-header/page-container-header.component.js
@@ -47,9 +47,10 @@ export default class PageContainerHeader extends Component {
return (
onClose && (
-
onClose()}
+ aria-label="close"
/>
)
);
diff --git a/ui/components/ui/text-field/text-field.component.js b/ui/components/ui/text-field/text-field.component.js
index 8720ff19b..44a96ee3f 100644
--- a/ui/components/ui/text-field/text-field.component.js
+++ b/ui/components/ui/text-field/text-field.component.js
@@ -246,7 +246,7 @@ TextField.propTypes = {
/**
* Show error message
*/
- error: PropTypes.string,
+ error: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
/**
* Add custom CSS class
*/
diff --git a/ui/pages/add-collectible/add-collectible.js b/ui/pages/add-collectible/add-collectible.js
index 71e4002d7..2587d1073 100644
--- a/ui/pages/add-collectible/add-collectible.js
+++ b/ui/pages/add-collectible/add-collectible.js
@@ -17,8 +17,13 @@ export default function AddCollectible() {
const t = useI18nContext();
const history = useHistory();
const dispatch = useDispatch();
+ const addressEnteredOnImportTokensPage =
+ history?.location?.state?.addressEnteredOnImportTokensPage;
+
+ const [address, setAddress] = useState(
+ addressEnteredOnImportTokensPage ?? '',
+ );
- const [address, setAddress] = useState('');
const [tokenId, setTokenId] = useState('');
const [disabled, setDisabled] = useState(true);
@@ -47,7 +52,7 @@ export default function AddCollectible() {
return (
{
handleAddCollectible();
}}
diff --git a/ui/pages/import-token/import-token.component.js b/ui/pages/import-token/import-token.component.js
index dc2dbf1fe..8faa500a0 100644
--- a/ui/pages/import-token/import-token.component.js
+++ b/ui/pages/import-token/import-token.component.js
@@ -8,6 +8,7 @@ import {
} from '../../helpers/utils/util';
import { tokenInfoGetter } from '../../helpers/utils/token-util';
import {
+ ADD_COLLECTIBLE_ROUTE,
CONFIRM_IMPORT_TOKEN_ROUTE,
EXPERIMENTAL_ROUTE,
} from '../../helpers/constants/routes';
@@ -46,6 +47,8 @@ class ImportToken extends Component {
rpcPrefs: PropTypes.object,
tokenList: PropTypes.object,
useTokenDetection: PropTypes.bool,
+ getTokenStandardAndDetails: PropTypes.func,
+ selectedAddress: PropTypes.string,
};
static defaultProps = {
@@ -62,6 +65,7 @@ class ImportToken extends Component {
customAddressError: null,
customSymbolError: null,
customDecimalsError: null,
+ collectibleAddressError: null,
forceEditSymbol: false,
symbolAutoFilled: false,
decimalAutoFilled: false,
@@ -126,13 +130,15 @@ class ImportToken extends Component {
customAddressError,
customSymbolError,
customDecimalsError,
+ collectibleAddressError,
} = this.state;
return (
tokenSelectorError ||
customAddressError ||
customSymbolError ||
- customDecimalsError
+ customDecimalsError ||
+ collectibleAddressError
);
}
@@ -186,11 +192,12 @@ class ImportToken extends Component {
this.handleCustomDecimalsChange(decimals);
}
- handleCustomAddressChange(value) {
+ async handleCustomAddressChange(value) {
const customAddress = value.trim();
this.setState({
customAddress,
customAddressError: null,
+ collectibleAddressError: null,
tokenSelectorError: null,
symbolAutoFilled: false,
decimalAutoFilled: false,
@@ -208,8 +215,23 @@ class ImportToken extends Component {
const isMainnetNetwork = this.props.chainId === '0x1';
+ let standard;
+ if (addressIsValid) {
+ try {
+ ({ standard } = await this.props.getTokenStandardAndDetails(
+ standardAddress,
+ this.props.selectedAddress,
+ ));
+ } catch (error) {
+ // ignore
+ }
+ }
+
+ const addressIsEmpty =
+ customAddress.length === 0 || customAddress === emptyAddr;
+
switch (true) {
- case !addressIsValid:
+ case !addressIsValid && !addressIsEmpty:
this.setState({
customAddressError: this.context.t('invalidAddress'),
customSymbol: '',
@@ -218,6 +240,28 @@ class ImportToken extends Component {
customDecimalsError: null,
});
+ break;
+ case process.env.COLLECTIBLES_V1 &&
+ (standard === 'ERC1155' || standard === 'ERC721'):
+ this.setState({
+ collectibleAddressError: this.context.t('collectibleAddressError', [
+
+ this.props.history.push({
+ pathname: ADD_COLLECTIBLE_ROUTE,
+ state: {
+ addressEnteredOnImportTokensPage: this.state.customAddress,
+ },
+ })
+ }
+ key="collectibleAddressError"
+ >
+ {this.context.t('importNFTPage')}
+ ,
+ ]),
+ });
+
break;
case isMainnetToken && !isMainnetNetwork:
this.setState({
@@ -242,7 +286,7 @@ class ImportToken extends Component {
break;
default:
- if (customAddress !== emptyAddr) {
+ if (!addressIsEmpty) {
this.attemptToAutoFillTokenParams(customAddress);
}
}
@@ -290,6 +334,7 @@ class ImportToken extends Component {
symbolAutoFilled,
decimalAutoFilled,
mainnetTokenWarning,
+ collectibleAddressError,
} = this.state;
const { chainId, rpcPrefs } = this.props;
@@ -330,7 +375,9 @@ class ImportToken extends Component {
type="text"
value={customAddress}
onChange={(e) => this.handleCustomAddressChange(e.target.value)}
- error={customAddressError || mainnetTokenWarning}
+ error={
+ customAddressError || mainnetTokenWarning || collectibleAddressError
+ }
fullWidth
autoFocus
margin="normal"
diff --git a/ui/pages/import-token/import-token.container.js b/ui/pages/import-token/import-token.container.js
index f6fa265f1..ae3d97228 100644
--- a/ui/pages/import-token/import-token.container.js
+++ b/ui/pages/import-token/import-token.container.js
@@ -1,6 +1,10 @@
import { connect } from 'react-redux';
-import { setPendingTokens, clearPendingTokens } from '../../store/actions';
+import {
+ setPendingTokens,
+ clearPendingTokens,
+ getTokenStandardAndDetails,
+} from '../../store/actions';
import { getMostRecentOverviewPage } from '../../ducks/history/history';
import {
getRpcPrefsForCurrentProvider,
@@ -17,6 +21,7 @@ const mapStateToProps = (state) => {
provider: { chainId },
useTokenDetection,
tokenList,
+ selectedAddress,
},
} = state;
const showSearchTabCustomNetwork =
@@ -33,13 +38,15 @@ const mapStateToProps = (state) => {
rpcPrefs: getRpcPrefsForCurrentProvider(state),
tokenList,
useTokenDetection,
+ selectedAddress,
};
};
-
const mapDispatchToProps = (dispatch) => {
return {
setPendingTokens: (tokens) => dispatch(setPendingTokens(tokens)),
clearPendingTokens: () => dispatch(clearPendingTokens()),
+ getTokenStandardAndDetails: (address, selectedAddress) =>
+ getTokenStandardAndDetails(address, selectedAddress, null),
};
};
diff --git a/ui/pages/import-token/import-token.test.js b/ui/pages/import-token/import-token.test.js
index c6be1f1bb..c03a9c486 100644
--- a/ui/pages/import-token/import-token.test.js
+++ b/ui/pages/import-token/import-token.test.js
@@ -1,110 +1,177 @@
import React from 'react';
-import { Provider } from 'react-redux';
-import sinon from 'sinon';
-import configureMockStore from 'redux-mock-store';
-import { mountWithRouter } from '../../../test/lib/render-helpers';
+import { fireEvent } from '@testing-library/react';
+import { renderWithProvider } from '../../../test/lib/render-helpers';
+import configureStore from '../../store/store';
+import {
+ setPendingTokens,
+ clearPendingTokens,
+ getTokenStandardAndDetails,
+} from '../../store/actions';
import ImportToken from './import-token.container';
+jest.mock('../../store/actions', () => ({
+ getTokenStandardAndDetails: jest
+ .fn()
+ .mockImplementation(() => Promise.resolve({ standard: 'ERC20' })),
+ setPendingTokens: jest
+ .fn()
+ .mockImplementation(() => ({ type: 'SET_PENDING_TOKENS' })),
+ clearPendingTokens: jest
+ .fn()
+ .mockImplementation(() => ({ type: 'CLEAR_PENDING_TOKENS' })),
+}));
+
describe('Import Token', () => {
- let wrapper;
-
- const state = {
- metamask: {
- tokens: [],
- },
- };
-
- const store = configureMockStore()(state);
-
+ const historyStub = jest.fn();
const props = {
history: {
- push: sinon.stub().callsFake(() => undefined),
+ push: historyStub,
},
- setPendingTokens: sinon.spy(),
- clearPendingTokens: sinon.spy(),
- tokens: [],
- identities: {},
- mostRecentOverviewPage: '/',
showSearchTab: true,
tokenList: {},
};
+ const render = () => {
+ const baseStore = {
+ metamask: {
+ tokens: [],
+ provider: { chainId: '0x1' },
+ frequentRpcListDetail: [],
+ identities: {},
+ selectedAddress: '0x1231231',
+ },
+ history: {
+ mostRecentOverviewPage: '/',
+ },
+ };
+
+ const store = configureStore(baseStore);
+
+ return renderWithProvider(, store);
+ };
+
describe('Import Token', () => {
- beforeAll(() => {
- wrapper = mountWithRouter(
-
-
- ,
- store,
- );
+ it('add Custom Token button is disabled when no fields are populated', () => {
+ const { getByText } = render();
+ const customTokenButton = getByText('Custom Token');
+ fireEvent.click(customTokenButton);
+ const submit = getByText('Add Custom Token');
- wrapper.find({ name: 'customToken' }).simulate('click');
- });
-
- afterEach(() => {
- props.history.push.reset();
- });
-
- it('next button is disabled when no fields are populated', () => {
- const nextButton = wrapper.find(
- '.button.btn-primary.page-container__footer-button',
- );
-
- expect(nextButton.props().disabled).toStrictEqual(true);
+ expect(submit).toBeDisabled();
});
it('edits token address', () => {
+ const { getByText } = render();
+ const customTokenButton = getByText('Custom Token');
+ fireEvent.click(customTokenButton);
+
const tokenAddress = '0x617b3f8050a0BD94b6b1da02B4384eE5B4DF13F4';
const event = { target: { value: tokenAddress } };
- const customAddress = wrapper.find('input#custom-address');
+ fireEvent.change(document.getElementById('custom-address'), event);
- customAddress.simulate('change', event);
- expect(
- wrapper.find('ImportToken').instance().state.customAddress,
- ).toStrictEqual(tokenAddress);
+ expect(document.getElementById('custom-address').value).toStrictEqual(
+ tokenAddress,
+ );
});
it('edits token symbol', () => {
+ const { getByText } = render();
+ const customTokenButton = getByText('Custom Token');
+ fireEvent.click(customTokenButton);
+
const tokenSymbol = 'META';
const event = { target: { value: tokenSymbol } };
- const customAddress = wrapper.find('#custom-symbol');
- customAddress.last().simulate('change', event);
+ fireEvent.change(document.getElementById('custom-symbol'), event);
- expect(
- wrapper.find('ImportToken').instance().state.customSymbol,
- ).toStrictEqual(tokenSymbol);
+ expect(document.getElementById('custom-symbol').value).toStrictEqual(
+ tokenSymbol,
+ );
});
it('edits token decimal precision', () => {
+ const { getByText } = render();
+ const customTokenButton = getByText('Custom Token');
+ fireEvent.click(customTokenButton);
+
const tokenPrecision = '2';
const event = { target: { value: tokenPrecision } };
- const customAddress = wrapper.find('#custom-decimals');
- customAddress.last().simulate('change', event);
+ fireEvent.change(document.getElementById('custom-decimals'), event);
- expect(
- wrapper.find('ImportToken').instance().state.customDecimals,
- ).toStrictEqual(Number(tokenPrecision));
- });
-
- it('next', () => {
- const nextButton = wrapper.find(
- '.button.btn-primary.page-container__footer-button',
- );
- nextButton.simulate('click');
-
- expect(props.setPendingTokens.calledOnce).toStrictEqual(true);
- expect(props.history.push.calledOnce).toStrictEqual(true);
- expect(props.history.push.getCall(0).args[0]).toStrictEqual(
- '/confirm-import-token',
+ expect(document.getElementById('custom-decimals').value).toStrictEqual(
+ tokenPrecision,
);
});
- it('cancels', () => {
- const cancelButton = wrapper.find('.page-container__header-close');
- cancelButton.simulate('click');
+ it('adds custom tokens successfully', async () => {
+ const { getByText } = render();
+ const customTokenButton = getByText('Custom Token');
+ fireEvent.click(customTokenButton);
- expect(props.clearPendingTokens.calledOnce).toStrictEqual(true);
- expect(props.history.push.getCall(0).args[0]).toStrictEqual('/');
+ const submit = getByText('Add Custom Token');
+ expect(submit).toBeDisabled();
+
+ const tokenAddress = '0x617b3f8050a0BD94b6b1da02B4384eE5B4DF13F4';
+ fireEvent.change(document.getElementById('custom-address'), {
+ target: { value: tokenAddress },
+ });
+ expect(submit).not.toBeDisabled();
+
+ const tokenSymbol = 'META';
+ fireEvent.change(document.getElementById('custom-symbol'), {
+ target: { value: tokenSymbol },
+ });
+
+ const tokenPrecision = '2';
+ await fireEvent.change(document.getElementById('custom-decimals'), {
+ target: { value: tokenPrecision },
+ });
+
+ expect(submit).not.toBeDisabled();
+ fireEvent.click(submit);
+ expect(setPendingTokens).toHaveBeenCalledWith({
+ customToken: {
+ address: tokenAddress,
+ decimals: Number(tokenPrecision),
+ symbol: tokenSymbol,
+ },
+ selectedTokens: {},
+ tokenAddressList: [],
+ });
+ expect(historyStub).toHaveBeenCalledWith('/confirm-import-token');
+ });
+
+ it('cancels out of import token flow', () => {
+ const { getByRole } = render();
+ const closeButton = getByRole('button', { name: 'close' });
+ fireEvent.click(closeButton);
+
+ expect(clearPendingTokens).toHaveBeenCalled();
+ expect(historyStub).toHaveBeenCalledWith('/');
+ });
+
+ it('sets and error when a token is an NFT', async () => {
+ process.env.COLLECTIBLES_V1 = true;
+ getTokenStandardAndDetails.mockImplementation(() =>
+ Promise.resolve({ standard: 'ERC721' }),
+ );
+
+ const { getByText } = render();
+ const customTokenButton = getByText('Custom Token');
+ fireEvent.click(customTokenButton);
+
+ const submit = getByText('Add Custom Token');
+ expect(submit).toBeDisabled();
+
+ const tokenAddress = '0x617b3f8050a0BD94b6b1da02B4384eE5B4DF13F4';
+ await fireEvent.change(document.getElementById('custom-address'), {
+ target: { value: tokenAddress },
+ });
+
+ expect(submit).toBeDisabled();
+
+ // The last part of this error message won't be found by getByText because it is wrapped as a link.
+ const errorMessage = getByText('This token is an NFT. Add on the');
+ expect(errorMessage).toBeInTheDocument();
});
});
});
diff --git a/ui/pages/import-token/index.scss b/ui/pages/import-token/index.scss
index b687b9170..178334fda 100644
--- a/ui/pages/import-token/index.scss
+++ b/ui/pages/import-token/index.scss
@@ -6,12 +6,12 @@
&__custom-token-form {
padding: 8px 16px 16px;
- input[type="number"]::-webkit-inner-spin-button {
+ input[type='number']::-webkit-inner-spin-button {
-webkit-appearance: none;
display: none;
}
- input[type="number"]:hover::-webkit-inner-spin-button {
+ input[type='number']:hover::-webkit-inner-spin-button {
-webkit-appearance: none;
display: none;
}
@@ -59,4 +59,13 @@
margin-bottom: 16px;
margin-top: 0;
}
+
+ &__collectible-address-error-link {
+ color: var(--primary-blue);
+ cursor: pointer;
+
+ &:hover {
+ color: var(--Blue-400);
+ }
+ }
}
diff --git a/ui/store/actions.js b/ui/store/actions.js
index 480c34ee0..6473d2c58 100644
--- a/ui/store/actions.js
+++ b/ui/store/actions.js
@@ -1418,6 +1418,18 @@ export async function checkAndUpdateSingleCollectibleOwnershipStatus(
);
}
+export async function getTokenStandardAndDetails(
+ address,
+ userAddress,
+ tokenId,
+) {
+ return await promisifiedBackground.getTokenStandardAndDetails(
+ address,
+ userAddress,
+ tokenId,
+ );
+}
+
export function removeToken(address) {
return async (dispatch) => {
dispatch(showLoadingIndication());
diff --git a/yarn.lock b/yarn.lock
index 0fe4f3931..036efd66e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2652,10 +2652,10 @@
web3 "^0.20.7"
web3-provider-engine "^16.0.3"
-"@metamask/controllers@^24.0.0":
- version "24.0.0"
- resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-24.0.0.tgz#380d4e10bd9b903afc38b041ea7e64b30ae34dab"
- integrity sha512-gOTpvxQTNTrXOUpGOiRKcqHUgnjSiTgJcwNLxenZWqPIixz7eI2qHnjO7ZaGbV/baK7SOa4rRGokvsCNV0mafA==
+"@metamask/controllers@^25.0.0":
+ version "25.0.0"
+ resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-25.0.0.tgz#764ba46a169f197253e35ca5273720c91eae7662"
+ integrity sha512-BfTRaK87Iha+EXlEsu330Jp9Wtx0gks2vA+q3Lu/AKGbcz2J0cokECNwN7uEe4GQLPLsfCIAkUIAlP3pRiCvjg==
dependencies:
"@ethereumjs/common" "^2.3.1"
"@ethereumjs/tx" "^3.2.1"