import React, { useState, useEffect } from 'react'; import { useSelector } from 'react-redux'; import { Alert, Text, TextInput, View, StyleSheet } from 'react-native'; import { fontStyles } from '../../../styles/common'; import Engine from '../../../core/Engine'; import { strings } from '../../../../locales/i18n'; import { isValidAddress } from 'ethereumjs-util'; import ActionView from '../ActionView'; import { isSmartContractAddress } from '../../../util/transactions'; import Device from '../../../util/device'; import AnalyticsV2 from '../../../util/analyticsV2'; import { useAppThemeFromContext, mockTheme } from '../../../util/theme'; const createStyles = (colors: any) => StyleSheet.create({ wrapper: { backgroundColor: colors.background.default, flex: 1, }, rowWrapper: { padding: 20, }, rowTitleText: { paddingBottom: 3, ...(fontStyles.normal as any), color: colors.text.default, }, textInput: { borderWidth: 1, borderRadius: 4, borderColor: colors.border.default, padding: 16, ...(fontStyles.normal as any), color: colors.text.default, }, warningText: { marginTop: 15, color: colors.error.default, ...(fontStyles.normal as any), }, }); interface AddCustomCollectibleProps { navigation?: any; collectibleContract?: { address: string; }; } const AddCustomCollectible = ({ navigation, collectibleContract, }: AddCustomCollectibleProps) => { const [mounted, setMounted] = useState(true); const [address, setAddress] = useState(''); const [tokenId, setTokenId] = useState(''); const [warningAddress, setWarningAddress] = useState(''); const [warningTokenId, setWarningTokenId] = useState(''); const [inputWidth, setInputWidth] = useState( Device.isAndroid() ? '99%' : undefined, ); const assetTokenIdInput = React.createRef() as any; const { colors, themeAppearance } = useAppThemeFromContext() || mockTheme; const styles = createStyles(colors); const selectedAddress = useSelector( (state: any) => state.engine.backgroundState.PreferencesController.selectedAddress, ); const chainId = useSelector( (state: any) => state.engine.backgroundState.NetworkController.provider.chainId, ); useEffect(() => { setMounted(true); // Workaround https://github.com/facebook/react-native/issues/9958 inputWidth && setTimeout(() => { mounted && setInputWidth('100%'); }, 100); collectibleContract && setAddress(collectibleContract.address); return () => { setMounted(false); }; }, [mounted, collectibleContract, inputWidth]); const getAnalyticsParams = () => { try { const { NetworkController } = Engine.context as any; const { type } = NetworkController?.state?.provider || {}; return { network_name: type, chain_id: chainId, }; } catch (error) { return {}; } }; const validateCustomCollectibleAddress = async (): Promise => { let validated = true; const isValidEthAddress = isValidAddress(address); if (address.length === 0) { setWarningAddress(strings('collectible.address_cant_be_empty')); validated = false; } else if (!isValidEthAddress) { setWarningAddress(strings('collectible.address_must_be_valid')); validated = false; } else if (!(await isSmartContractAddress(address, chainId))) { setWarningAddress(strings('collectible.address_must_be_smart_contract')); validated = false; } else { setWarningAddress(``); } return validated; }; const validateCustomCollectibleTokenId = (): boolean => { let validated = false; if (tokenId.length === 0) { setWarningTokenId(strings('collectible.token_id_cant_be_empty')); } else { setWarningTokenId(``); validated = true; } return validated; }; const validateCustomCollectible = async (): Promise => { const validatedAddress = await validateCustomCollectibleAddress(); const validatedTokenId = validateCustomCollectibleTokenId(); return validatedAddress && validatedTokenId; }; /** * Method to validate collectible ownership. * * @returns Promise that resolves ownershio as a boolean. */ const validateCollectibleOwnership = async (): Promise => { try { const { CollectiblesController } = Engine.context as any; const isOwner = await CollectiblesController.isCollectibleOwner( selectedAddress, address, tokenId, ); if (!isOwner) Alert.alert( strings('collectible.not_owner_error_title'), strings('collectible.not_owner_error'), ); return isOwner; } catch { Alert.alert( strings('collectible.ownership_verification_error_title'), strings('collectible.ownership_verification_error'), ); return false; } }; const addCollectible = async (): Promise => { if (!(await validateCustomCollectible())) return; if (!(await validateCollectibleOwnership())) return; const { CollectiblesController } = Engine.context as any; CollectiblesController.addCollectible(address, tokenId); AnalyticsV2.trackEvent( AnalyticsV2.ANALYTICS_EVENTS.COLLECTIBLE_ADDED, getAnalyticsParams(), ); navigation.goBack(); }; const cancelAddCollectible = (): void => { navigation.goBack(); }; const onAddressChange = (newAddress: string): void => { setAddress(newAddress); }; const onTokenIdChange = (newTokenId: string): void => { setTokenId(newTokenId); }; const jumpToAssetTokenId = (): void => { assetTokenIdInput.current?.focus(); }; return ( {strings('collectible.collectible_address')} {warningAddress} {strings('collectible.collectible_token_id')} {warningTokenId} ); }; export default AddCustomCollectible;