Skip to content

Commit

Permalink
Merge pull request #541 from soroswap/feature/validateContractID
Browse files Browse the repository at this point in the history
✨Add unsafe token modal and contractID validation
  • Loading branch information
chopan123 authored Sep 13, 2024
2 parents 4ffda99 + 28bec0b commit 77209f3
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 2 deletions.
22 changes: 20 additions & 2 deletions src/components/SearchModal/CurrencySearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import Column from '../Column';
import Row, { RowBetween } from '../Row';
import CurrencyList, { CurrencyRow, formatAnalyticsEventProperties } from './CurrencyList';
import { PaddedColumn, SearchInput, Separator } from './styleds';
import UnsafeTokenModalContent from 'components/UnsafeTokenModal/UnsafeTokenModal';

const ContentWrapper = styled(Column)<{ modalheight?: number }>`
overflow: hidden;
Expand Down Expand Up @@ -86,10 +87,11 @@ export function CurrencySearch({
const [searchQuery, setSearchQuery] = useState<string>('');
const debouncedQuery = useDebounce(searchQuery, 200);
const isAddressSearch = isAddress(debouncedQuery);
const { token: searchToken, needsWrapping, isLoading: isLoadingToken } = useToken(debouncedQuery);
const { token: searchToken, needsWrapping, isLoading: isLoadingToken, tokenIsSafe } = useToken(debouncedQuery);
const { tokensAsMap: allTokens } = useAllTokens();

const [showUserAddedTokenModal, setShowUserAddedTokenModal] = useState<boolean>(false);
const [showUnsafeTokenModal, setShowUnsafeTokenModal] = useState<boolean>(false);
const [showWrapStellarAssetModal, setShowWrapStellarAssetModal] = useState<boolean>(false);

//This sends and event when a token is searched to google analytics
Expand Down Expand Up @@ -129,6 +131,11 @@ export function CurrencySearch({

const searchCurrencies = useSortTokensByQuery(debouncedQuery, filteredTokens);

const handleConfirmUnsafeToken = () => {
setShowUnsafeTokenModal(false);
setShowUserAddedTokenModal(true);
}

const handleConfirmAddUserToken = () => {
if (!searchToken) return;
addUserToken(searchToken, sorobanContext);
Expand All @@ -144,9 +151,12 @@ export function CurrencySearch({
(currency: TokenType, hasWarning?: boolean) => {
if (needsWrapping) {
setShowWrapStellarAssetModal(true);
} else if (!allTokens[currency.contract] && !tokenIsSafe) {
setShowUnsafeTokenModal(true);
} else if (!allTokens[currency.contract]) {
setShowUserAddedTokenModal(true);
} else {
}
else {
onCurrencySelect(currency, hasWarning);
if (!hasWarning) onDismiss();
}
Expand Down Expand Up @@ -193,6 +203,14 @@ export function CurrencySearch({

return (
<>
<Modal open={showUnsafeTokenModal} onClose={() => setShowUnsafeTokenModal(false)}>
<div>
<UnsafeTokenModalContent
onConfirm={handleConfirmUnsafeToken}
onDismiss={() => setShowUnsafeTokenModal(false)}
isSafe={tokenIsSafe} />
</div>
</Modal>
<Modal open={showUserAddedTokenModal} onClose={() => setShowUserAddedTokenModal(false)}>
<div>
<UserAddedTokenModalContent
Expand Down
50 changes: 50 additions & 0 deletions src/components/UnsafeTokenModal/UnsafeTokenModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

import { Box, useTheme } from 'soroswap-ui';
import { ButtonPrimary } from 'components/Buttons/Button';
import { CloseButton } from 'components/Buttons/CloseButton';
import { AutoColumn } from 'components/Column';
import { RowBetween } from 'components/Row';
import { SubHeaderLarge, SubHeaderSmall } from 'components/Text';
import { Wrapper } from 'components/TransactionConfirmationModal/ModalStyles';
import { AlertTriangle } from 'react-feather';

interface Props {
onDismiss: () => void;
onConfirm: () => void;
isSafe: boolean | undefined;
}

const UnsafeTokenModalContent = ({ onDismiss, onConfirm, isSafe }: Props) => {
const theme = useTheme();
return (
<Wrapper>
<AutoColumn gap="12px">
<RowBetween>
<div />
<CloseButton onClick={onDismiss} />
</RowBetween>
<Box display="flex" justifyContent="center">
<AlertTriangle size="64px" stroke={theme.palette.customBackground.accentCritical} />
</Box>
<AutoColumn gap="12px" justify="center">
<SubHeaderLarge color="textPrimary" textAlign="center">
Unsafe token!
</SubHeaderLarge>
{isSafe === false && (
<SubHeaderSmall color={"textSecondary"} textAlign="center" marginBottom="12px">
The chosen token has been identified as potentially unsafe due to a mismatch between its contract ID and the expected CODE:ISSUER combination indicated by its name.
</SubHeaderSmall>
)}
{isSafe === undefined && (
<SubHeaderSmall color={"textSecondary"} textAlign="center" marginBottom="12px">
This token issuer can&apos;t be verified.
</SubHeaderSmall>
)}
</AutoColumn>
<ButtonPrimary onClick={onConfirm}>Accept risk</ButtonPrimary>
</AutoColumn>
</Wrapper>
);
};

export default UnsafeTokenModalContent;
15 changes: 15 additions & 0 deletions src/hooks/tokens/useToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import useSWR, { useSWRConfig } from 'swr';
import useSWRImmutable from 'swr/immutable';
import { useAllTokens } from './useAllTokens';
import { getToken, isClassicStellarAsset } from './utils';
import { Asset } from '@stellar/stellar-sdk';

export const findToken = async (
tokenAddress: string | undefined,
Expand Down Expand Up @@ -65,6 +66,7 @@ const revalidateKeysCondition = (key: any) => {
//Returns token from map (user added + api) or network
export function useToken(tokenAddress: string | undefined) {
const sorobanContext = useSorobanReact();
const { activeChain: network } = sorobanContext;
const { tokensAsMap } = useAllTokens();

const { data: name } = useSWR(
Expand Down Expand Up @@ -95,6 +97,18 @@ export function useToken(tokenAddress: string | undefined) {

const needsWrapping = !data && isStellarClassicAsset;

const checkContractId = (contractId: string, code: string, issuer: string): boolean | undefined => {
if (!issuer) {
return undefined;
}
const asset = new Asset(code, issuer);
if (asset.contractId(network?.networkPassphrase!) === contractId) {
return true;
} else {
return false;
}
}
const isSafe = data ? checkContractId(data.contract, data.code, data.issuer!) : false;

const needsWrappingOnAddLiquidity = (!data && isStellarClassicAsset) || !name;

Expand All @@ -120,6 +134,7 @@ export function useToken(tokenAddress: string | undefined) {
//if not data and AssetExists return isWrapped: false
return {
token: data ?? newTokenData,
tokenIsSafe: isSafe,
needsWrapping,
isLoading: bothLoading,
isError: error,
Expand Down

0 comments on commit 77209f3

Please sign in to comment.