mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Add error that redirects users to Import NFT page when they attempt to add an NFT on the Import Token page (#13271)
* Add error that redirects users to Import NFT page when they attempt to add an NFT on the Import Token page
This commit is contained in:
parent
9a3c917a48
commit
f7849a0b7c
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "Προσθήκη σημειώματος"
|
"message": "Προσθήκη σημειώματος"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "Προσθήκη NFT"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "Προσθήκη Δικτύου"
|
"message": "Προσθήκη Δικτύου"
|
||||||
},
|
},
|
||||||
|
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "Add memo"
|
"message": "Add memo"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "Add NFT"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "Add Network"
|
"message": "Add Network"
|
||||||
},
|
},
|
||||||
@ -458,6 +455,10 @@
|
|||||||
"close": {
|
"close": {
|
||||||
"message": "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": {
|
"confirm": {
|
||||||
"message": "Confirm"
|
"message": "Confirm"
|
||||||
},
|
},
|
||||||
@ -1402,6 +1403,12 @@
|
|||||||
"importMyWallet": {
|
"importMyWallet": {
|
||||||
"message": "Import My Wallet"
|
"message": "Import My Wallet"
|
||||||
},
|
},
|
||||||
|
"importNFT": {
|
||||||
|
"message": "Import NFT"
|
||||||
|
},
|
||||||
|
"importNFTPage": {
|
||||||
|
"message": "Import NFT page"
|
||||||
|
},
|
||||||
"importNFTs": {
|
"importNFTs": {
|
||||||
"message": "Import NFTs"
|
"message": "Import NFTs"
|
||||||
},
|
},
|
||||||
|
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "Ajouter un mémo"
|
"message": "Ajouter un mémo"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "Ajouter un NFT"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "Ajouter un réseau"
|
"message": "Ajouter un réseau"
|
||||||
},
|
},
|
||||||
|
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "मेमो जोड़ें"
|
"message": "मेमो जोड़ें"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "NFT जोड़ें"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "नेटवर्क जोड़ें"
|
"message": "नेटवर्क जोड़ें"
|
||||||
},
|
},
|
||||||
|
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "Tambahkan memo"
|
"message": "Tambahkan memo"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "Tambahkan NFT"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "Tambahkan Jaringan"
|
"message": "Tambahkan Jaringan"
|
||||||
},
|
},
|
||||||
|
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "メモを追加"
|
"message": "メモを追加"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "NFTを追加"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "ネットワークの追加"
|
"message": "ネットワークの追加"
|
||||||
},
|
},
|
||||||
|
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "메모 추가"
|
"message": "메모 추가"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "NFT 추가"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "네트워크 추가"
|
"message": "네트워크 추가"
|
||||||
},
|
},
|
||||||
|
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "Добавить примечание"
|
"message": "Добавить примечание"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "Добавить NFT"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "Добавить сеть"
|
"message": "Добавить сеть"
|
||||||
},
|
},
|
||||||
|
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "Magdagdag ng memo"
|
"message": "Magdagdag ng memo"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "Magdagdag ng NFT"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "Magdagdag ng Network"
|
"message": "Magdagdag ng Network"
|
||||||
},
|
},
|
||||||
|
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "Not ekleyin"
|
"message": "Not ekleyin"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "NFT ekleyin"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "Ağ ekleyin"
|
"message": "Ağ ekleyin"
|
||||||
},
|
},
|
||||||
|
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "Thêm bản ghi nhớ"
|
"message": "Thêm bản ghi nhớ"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "Thêm NFT"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "Thêm mạng"
|
"message": "Thêm mạng"
|
||||||
},
|
},
|
||||||
|
@ -140,9 +140,6 @@
|
|||||||
"addMemo": {
|
"addMemo": {
|
||||||
"message": "添加备忘录"
|
"message": "添加备忘录"
|
||||||
},
|
},
|
||||||
"addNFT": {
|
|
||||||
"message": "添加NFT"
|
|
||||||
},
|
|
||||||
"addNetwork": {
|
"addNetwork": {
|
||||||
"message": "添加网络"
|
"message": "添加网络"
|
||||||
},
|
},
|
||||||
|
@ -220,22 +220,22 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
onNetworkStateChange: this.networkController.store.subscribe.bind(
|
onNetworkStateChange: this.networkController.store.subscribe.bind(
|
||||||
this.networkController.store,
|
this.networkController.store,
|
||||||
),
|
),
|
||||||
getAssetName: this.assetsContractController.getAssetName.bind(
|
getERC721AssetName: this.assetsContractController.getERC721AssetName.bind(
|
||||||
this.assetsContractController,
|
this.assetsContractController,
|
||||||
),
|
),
|
||||||
getAssetSymbol: this.assetsContractController.getAssetSymbol.bind(
|
getERC721AssetSymbol: this.assetsContractController.getERC721AssetSymbol.bind(
|
||||||
this.assetsContractController,
|
this.assetsContractController,
|
||||||
),
|
),
|
||||||
getCollectibleTokenURI: this.assetsContractController.getCollectibleTokenURI.bind(
|
getERC721TokenURI: this.assetsContractController.getERC721TokenURI.bind(
|
||||||
this.assetsContractController,
|
this.assetsContractController,
|
||||||
),
|
),
|
||||||
getOwnerOf: this.assetsContractController.getOwnerOf.bind(
|
getERC721OwnerOf: this.assetsContractController.getERC721OwnerOf.bind(
|
||||||
this.assetsContractController,
|
this.assetsContractController,
|
||||||
),
|
),
|
||||||
balanceOfERC1155Collectible: this.assetsContractController.balanceOfERC1155Collectible.bind(
|
getERC1155BalanceOf: this.assetsContractController.getERC1155BalanceOf.bind(
|
||||||
this.assetsContractController,
|
this.assetsContractController,
|
||||||
),
|
),
|
||||||
uriERC1155Collectible: this.assetsContractController.uriERC1155Collectible.bind(
|
getERC1155TokenURI: this.assetsContractController.getERC1155TokenURI.bind(
|
||||||
this.assetsContractController,
|
this.assetsContractController,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@ -1040,6 +1040,7 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
appStateController,
|
appStateController,
|
||||||
collectiblesController,
|
collectiblesController,
|
||||||
collectibleDetectionController,
|
collectibleDetectionController,
|
||||||
|
assetsContractController,
|
||||||
currencyRateController,
|
currencyRateController,
|
||||||
detectTokensController,
|
detectTokensController,
|
||||||
ensController,
|
ensController,
|
||||||
@ -1192,6 +1193,11 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
preferencesController,
|
preferencesController,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// AssetsContractController
|
||||||
|
getTokenStandardAndDetails: assetsContractController.getTokenStandardAndDetails.bind(
|
||||||
|
assetsContractController,
|
||||||
|
),
|
||||||
|
|
||||||
// CollectiblesController
|
// CollectiblesController
|
||||||
addCollectible: collectiblesController.addCollectible.bind(
|
addCollectible: collectiblesController.addCollectible.bind(
|
||||||
collectiblesController,
|
collectiblesController,
|
||||||
|
@ -107,7 +107,7 @@
|
|||||||
"@keystonehq/metamask-airgapped-keyring": "0.2.1",
|
"@keystonehq/metamask-airgapped-keyring": "0.2.1",
|
||||||
"@material-ui/core": "^4.11.0",
|
"@material-ui/core": "^4.11.0",
|
||||||
"@metamask/contract-metadata": "^1.31.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-ledger-bridge-keyring": "^0.10.0",
|
||||||
"@metamask/eth-token-tracker": "^3.0.1",
|
"@metamask/eth-token-tracker": "^3.0.1",
|
||||||
"@metamask/etherscan-link": "^2.1.0",
|
"@metamask/etherscan-link": "^2.1.0",
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
right: 16px;
|
right: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '\00D7';
|
content: '\00D7';
|
||||||
|
@ -47,9 +47,10 @@ export default class PageContainerHeader extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
onClose && (
|
onClose && (
|
||||||
<div
|
<button
|
||||||
className="page-container__header-close"
|
className="page-container__header-close"
|
||||||
onClick={() => onClose()}
|
onClick={() => onClose()}
|
||||||
|
aria-label="close"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -246,7 +246,7 @@ TextField.propTypes = {
|
|||||||
/**
|
/**
|
||||||
* Show error message
|
* Show error message
|
||||||
*/
|
*/
|
||||||
error: PropTypes.string,
|
error: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
||||||
/**
|
/**
|
||||||
* Add custom CSS class
|
* Add custom CSS class
|
||||||
*/
|
*/
|
||||||
|
@ -17,8 +17,13 @@ export default function AddCollectible() {
|
|||||||
const t = useI18nContext();
|
const t = useI18nContext();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const addressEnteredOnImportTokensPage =
|
||||||
|
history?.location?.state?.addressEnteredOnImportTokensPage;
|
||||||
|
|
||||||
|
const [address, setAddress] = useState(
|
||||||
|
addressEnteredOnImportTokensPage ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
const [address, setAddress] = useState('');
|
|
||||||
const [tokenId, setTokenId] = useState('');
|
const [tokenId, setTokenId] = useState('');
|
||||||
const [disabled, setDisabled] = useState(true);
|
const [disabled, setDisabled] = useState(true);
|
||||||
|
|
||||||
@ -47,7 +52,7 @@ export default function AddCollectible() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer
|
<PageContainer
|
||||||
title={t('addNFT')}
|
title={t('importNFT')}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
handleAddCollectible();
|
handleAddCollectible();
|
||||||
}}
|
}}
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
} from '../../helpers/utils/util';
|
} from '../../helpers/utils/util';
|
||||||
import { tokenInfoGetter } from '../../helpers/utils/token-util';
|
import { tokenInfoGetter } from '../../helpers/utils/token-util';
|
||||||
import {
|
import {
|
||||||
|
ADD_COLLECTIBLE_ROUTE,
|
||||||
CONFIRM_IMPORT_TOKEN_ROUTE,
|
CONFIRM_IMPORT_TOKEN_ROUTE,
|
||||||
EXPERIMENTAL_ROUTE,
|
EXPERIMENTAL_ROUTE,
|
||||||
} from '../../helpers/constants/routes';
|
} from '../../helpers/constants/routes';
|
||||||
@ -46,6 +47,8 @@ class ImportToken extends Component {
|
|||||||
rpcPrefs: PropTypes.object,
|
rpcPrefs: PropTypes.object,
|
||||||
tokenList: PropTypes.object,
|
tokenList: PropTypes.object,
|
||||||
useTokenDetection: PropTypes.bool,
|
useTokenDetection: PropTypes.bool,
|
||||||
|
getTokenStandardAndDetails: PropTypes.func,
|
||||||
|
selectedAddress: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -62,6 +65,7 @@ class ImportToken extends Component {
|
|||||||
customAddressError: null,
|
customAddressError: null,
|
||||||
customSymbolError: null,
|
customSymbolError: null,
|
||||||
customDecimalsError: null,
|
customDecimalsError: null,
|
||||||
|
collectibleAddressError: null,
|
||||||
forceEditSymbol: false,
|
forceEditSymbol: false,
|
||||||
symbolAutoFilled: false,
|
symbolAutoFilled: false,
|
||||||
decimalAutoFilled: false,
|
decimalAutoFilled: false,
|
||||||
@ -126,13 +130,15 @@ class ImportToken extends Component {
|
|||||||
customAddressError,
|
customAddressError,
|
||||||
customSymbolError,
|
customSymbolError,
|
||||||
customDecimalsError,
|
customDecimalsError,
|
||||||
|
collectibleAddressError,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
tokenSelectorError ||
|
tokenSelectorError ||
|
||||||
customAddressError ||
|
customAddressError ||
|
||||||
customSymbolError ||
|
customSymbolError ||
|
||||||
customDecimalsError
|
customDecimalsError ||
|
||||||
|
collectibleAddressError
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,11 +192,12 @@ class ImportToken extends Component {
|
|||||||
this.handleCustomDecimalsChange(decimals);
|
this.handleCustomDecimalsChange(decimals);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCustomAddressChange(value) {
|
async handleCustomAddressChange(value) {
|
||||||
const customAddress = value.trim();
|
const customAddress = value.trim();
|
||||||
this.setState({
|
this.setState({
|
||||||
customAddress,
|
customAddress,
|
||||||
customAddressError: null,
|
customAddressError: null,
|
||||||
|
collectibleAddressError: null,
|
||||||
tokenSelectorError: null,
|
tokenSelectorError: null,
|
||||||
symbolAutoFilled: false,
|
symbolAutoFilled: false,
|
||||||
decimalAutoFilled: false,
|
decimalAutoFilled: false,
|
||||||
@ -208,8 +215,23 @@ class ImportToken extends Component {
|
|||||||
|
|
||||||
const isMainnetNetwork = this.props.chainId === '0x1';
|
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) {
|
switch (true) {
|
||||||
case !addressIsValid:
|
case !addressIsValid && !addressIsEmpty:
|
||||||
this.setState({
|
this.setState({
|
||||||
customAddressError: this.context.t('invalidAddress'),
|
customAddressError: this.context.t('invalidAddress'),
|
||||||
customSymbol: '',
|
customSymbol: '',
|
||||||
@ -218,6 +240,28 @@ class ImportToken extends Component {
|
|||||||
customDecimalsError: null,
|
customDecimalsError: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
case process.env.COLLECTIBLES_V1 &&
|
||||||
|
(standard === 'ERC1155' || standard === 'ERC721'):
|
||||||
|
this.setState({
|
||||||
|
collectibleAddressError: this.context.t('collectibleAddressError', [
|
||||||
|
<a
|
||||||
|
className="import-token__collectible-address-error-link"
|
||||||
|
onClick={() =>
|
||||||
|
this.props.history.push({
|
||||||
|
pathname: ADD_COLLECTIBLE_ROUTE,
|
||||||
|
state: {
|
||||||
|
addressEnteredOnImportTokensPage: this.state.customAddress,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
key="collectibleAddressError"
|
||||||
|
>
|
||||||
|
{this.context.t('importNFTPage')}
|
||||||
|
</a>,
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case isMainnetToken && !isMainnetNetwork:
|
case isMainnetToken && !isMainnetNetwork:
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -242,7 +286,7 @@ class ImportToken extends Component {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (customAddress !== emptyAddr) {
|
if (!addressIsEmpty) {
|
||||||
this.attemptToAutoFillTokenParams(customAddress);
|
this.attemptToAutoFillTokenParams(customAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -290,6 +334,7 @@ class ImportToken extends Component {
|
|||||||
symbolAutoFilled,
|
symbolAutoFilled,
|
||||||
decimalAutoFilled,
|
decimalAutoFilled,
|
||||||
mainnetTokenWarning,
|
mainnetTokenWarning,
|
||||||
|
collectibleAddressError,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const { chainId, rpcPrefs } = this.props;
|
const { chainId, rpcPrefs } = this.props;
|
||||||
@ -330,7 +375,9 @@ class ImportToken extends Component {
|
|||||||
type="text"
|
type="text"
|
||||||
value={customAddress}
|
value={customAddress}
|
||||||
onChange={(e) => this.handleCustomAddressChange(e.target.value)}
|
onChange={(e) => this.handleCustomAddressChange(e.target.value)}
|
||||||
error={customAddressError || mainnetTokenWarning}
|
error={
|
||||||
|
customAddressError || mainnetTokenWarning || collectibleAddressError
|
||||||
|
}
|
||||||
fullWidth
|
fullWidth
|
||||||
autoFocus
|
autoFocus
|
||||||
margin="normal"
|
margin="normal"
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import { connect } from 'react-redux';
|
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 { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||||
import {
|
import {
|
||||||
getRpcPrefsForCurrentProvider,
|
getRpcPrefsForCurrentProvider,
|
||||||
@ -17,6 +21,7 @@ const mapStateToProps = (state) => {
|
|||||||
provider: { chainId },
|
provider: { chainId },
|
||||||
useTokenDetection,
|
useTokenDetection,
|
||||||
tokenList,
|
tokenList,
|
||||||
|
selectedAddress,
|
||||||
},
|
},
|
||||||
} = state;
|
} = state;
|
||||||
const showSearchTabCustomNetwork =
|
const showSearchTabCustomNetwork =
|
||||||
@ -33,13 +38,15 @@ const mapStateToProps = (state) => {
|
|||||||
rpcPrefs: getRpcPrefsForCurrentProvider(state),
|
rpcPrefs: getRpcPrefsForCurrentProvider(state),
|
||||||
tokenList,
|
tokenList,
|
||||||
useTokenDetection,
|
useTokenDetection,
|
||||||
|
selectedAddress,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
return {
|
return {
|
||||||
setPendingTokens: (tokens) => dispatch(setPendingTokens(tokens)),
|
setPendingTokens: (tokens) => dispatch(setPendingTokens(tokens)),
|
||||||
clearPendingTokens: () => dispatch(clearPendingTokens()),
|
clearPendingTokens: () => dispatch(clearPendingTokens()),
|
||||||
|
getTokenStandardAndDetails: (address, selectedAddress) =>
|
||||||
|
getTokenStandardAndDetails(address, selectedAddress, null),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,110 +1,177 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { fireEvent } from '@testing-library/react';
|
||||||
import sinon from 'sinon';
|
import { renderWithProvider } from '../../../test/lib/render-helpers';
|
||||||
import configureMockStore from 'redux-mock-store';
|
import configureStore from '../../store/store';
|
||||||
import { mountWithRouter } from '../../../test/lib/render-helpers';
|
import {
|
||||||
|
setPendingTokens,
|
||||||
|
clearPendingTokens,
|
||||||
|
getTokenStandardAndDetails,
|
||||||
|
} from '../../store/actions';
|
||||||
import ImportToken from './import-token.container';
|
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', () => {
|
describe('Import Token', () => {
|
||||||
let wrapper;
|
const historyStub = jest.fn();
|
||||||
|
|
||||||
const state = {
|
|
||||||
metamask: {
|
|
||||||
tokens: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const store = configureMockStore()(state);
|
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
history: {
|
history: {
|
||||||
push: sinon.stub().callsFake(() => undefined),
|
push: historyStub,
|
||||||
},
|
},
|
||||||
setPendingTokens: sinon.spy(),
|
|
||||||
clearPendingTokens: sinon.spy(),
|
|
||||||
tokens: [],
|
|
||||||
identities: {},
|
|
||||||
mostRecentOverviewPage: '/',
|
|
||||||
showSearchTab: true,
|
showSearchTab: true,
|
||||||
tokenList: {},
|
tokenList: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const render = () => {
|
||||||
|
const baseStore = {
|
||||||
|
metamask: {
|
||||||
|
tokens: [],
|
||||||
|
provider: { chainId: '0x1' },
|
||||||
|
frequentRpcListDetail: [],
|
||||||
|
identities: {},
|
||||||
|
selectedAddress: '0x1231231',
|
||||||
|
},
|
||||||
|
history: {
|
||||||
|
mostRecentOverviewPage: '/',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = configureStore(baseStore);
|
||||||
|
|
||||||
|
return renderWithProvider(<ImportToken {...props} />, store);
|
||||||
|
};
|
||||||
|
|
||||||
describe('Import Token', () => {
|
describe('Import Token', () => {
|
||||||
beforeAll(() => {
|
it('add Custom Token button is disabled when no fields are populated', () => {
|
||||||
wrapper = mountWithRouter(
|
const { getByText } = render();
|
||||||
<Provider store={store}>
|
const customTokenButton = getByText('Custom Token');
|
||||||
<ImportToken.WrappedComponent {...props} />
|
fireEvent.click(customTokenButton);
|
||||||
</Provider>,
|
const submit = getByText('Add Custom Token');
|
||||||
store,
|
|
||||||
);
|
|
||||||
|
|
||||||
wrapper.find({ name: 'customToken' }).simulate('click');
|
expect(submit).toBeDisabled();
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('edits token address', () => {
|
it('edits token address', () => {
|
||||||
|
const { getByText } = render();
|
||||||
|
const customTokenButton = getByText('Custom Token');
|
||||||
|
fireEvent.click(customTokenButton);
|
||||||
|
|
||||||
const tokenAddress = '0x617b3f8050a0BD94b6b1da02B4384eE5B4DF13F4';
|
const tokenAddress = '0x617b3f8050a0BD94b6b1da02B4384eE5B4DF13F4';
|
||||||
const event = { target: { value: tokenAddress } };
|
const event = { target: { value: tokenAddress } };
|
||||||
const customAddress = wrapper.find('input#custom-address');
|
fireEvent.change(document.getElementById('custom-address'), event);
|
||||||
|
|
||||||
customAddress.simulate('change', event);
|
expect(document.getElementById('custom-address').value).toStrictEqual(
|
||||||
expect(
|
tokenAddress,
|
||||||
wrapper.find('ImportToken').instance().state.customAddress,
|
);
|
||||||
).toStrictEqual(tokenAddress);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('edits token symbol', () => {
|
it('edits token symbol', () => {
|
||||||
|
const { getByText } = render();
|
||||||
|
const customTokenButton = getByText('Custom Token');
|
||||||
|
fireEvent.click(customTokenButton);
|
||||||
|
|
||||||
const tokenSymbol = 'META';
|
const tokenSymbol = 'META';
|
||||||
const event = { target: { value: tokenSymbol } };
|
const event = { target: { value: tokenSymbol } };
|
||||||
const customAddress = wrapper.find('#custom-symbol');
|
fireEvent.change(document.getElementById('custom-symbol'), event);
|
||||||
customAddress.last().simulate('change', event);
|
|
||||||
|
|
||||||
expect(
|
expect(document.getElementById('custom-symbol').value).toStrictEqual(
|
||||||
wrapper.find('ImportToken').instance().state.customSymbol,
|
tokenSymbol,
|
||||||
).toStrictEqual(tokenSymbol);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('edits token decimal precision', () => {
|
it('edits token decimal precision', () => {
|
||||||
|
const { getByText } = render();
|
||||||
|
const customTokenButton = getByText('Custom Token');
|
||||||
|
fireEvent.click(customTokenButton);
|
||||||
|
|
||||||
const tokenPrecision = '2';
|
const tokenPrecision = '2';
|
||||||
const event = { target: { value: tokenPrecision } };
|
const event = { target: { value: tokenPrecision } };
|
||||||
const customAddress = wrapper.find('#custom-decimals');
|
fireEvent.change(document.getElementById('custom-decimals'), event);
|
||||||
customAddress.last().simulate('change', event);
|
|
||||||
|
|
||||||
expect(
|
expect(document.getElementById('custom-decimals').value).toStrictEqual(
|
||||||
wrapper.find('ImportToken').instance().state.customDecimals,
|
tokenPrecision,
|
||||||
).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',
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cancels', () => {
|
it('adds custom tokens successfully', async () => {
|
||||||
const cancelButton = wrapper.find('.page-container__header-close');
|
const { getByText } = render();
|
||||||
cancelButton.simulate('click');
|
const customTokenButton = getByText('Custom Token');
|
||||||
|
fireEvent.click(customTokenButton);
|
||||||
|
|
||||||
expect(props.clearPendingTokens.calledOnce).toStrictEqual(true);
|
const submit = getByText('Add Custom Token');
|
||||||
expect(props.history.push.getCall(0).args[0]).toStrictEqual('/');
|
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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,12 +6,12 @@
|
|||||||
&__custom-token-form {
|
&__custom-token-form {
|
||||||
padding: 8px 16px 16px;
|
padding: 8px 16px 16px;
|
||||||
|
|
||||||
input[type="number"]::-webkit-inner-spin-button {
|
input[type='number']::-webkit-inner-spin-button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="number"]:hover::-webkit-inner-spin-button {
|
input[type='number']:hover::-webkit-inner-spin-button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -59,4 +59,13 @@
|
|||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__collectible-address-error-link {
|
||||||
|
color: var(--primary-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--Blue-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
export function removeToken(address) {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
dispatch(showLoadingIndication());
|
dispatch(showLoadingIndication());
|
||||||
|
@ -2652,10 +2652,10 @@
|
|||||||
web3 "^0.20.7"
|
web3 "^0.20.7"
|
||||||
web3-provider-engine "^16.0.3"
|
web3-provider-engine "^16.0.3"
|
||||||
|
|
||||||
"@metamask/controllers@^24.0.0":
|
"@metamask/controllers@^25.0.0":
|
||||||
version "24.0.0"
|
version "25.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-24.0.0.tgz#380d4e10bd9b903afc38b041ea7e64b30ae34dab"
|
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-25.0.0.tgz#764ba46a169f197253e35ca5273720c91eae7662"
|
||||||
integrity sha512-gOTpvxQTNTrXOUpGOiRKcqHUgnjSiTgJcwNLxenZWqPIixz7eI2qHnjO7ZaGbV/baK7SOa4rRGokvsCNV0mafA==
|
integrity sha512-BfTRaK87Iha+EXlEsu330Jp9Wtx0gks2vA+q3Lu/AKGbcz2J0cokECNwN7uEe4GQLPLsfCIAkUIAlP3pRiCvjg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@ethereumjs/common" "^2.3.1"
|
"@ethereumjs/common" "^2.3.1"
|
||||||
"@ethereumjs/tx" "^3.2.1"
|
"@ethereumjs/tx" "^3.2.1"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user