diff --git a/extension/package.json b/extension/package.json index bd945bf112..b68e6e89b7 100644 --- a/extension/package.json +++ b/extension/package.json @@ -23,7 +23,6 @@ "@shared/api": "1.0.0", "@shared/constants": "1.0.0", "@shared/helpers": "1.0.0", - "@stellar-asset-lists/sdk": "^1.0.0", "@stellar/design-system": "^1.1.2", "@stellar/wallet-sdk": "v0.11.0-beta.1", "@testing-library/react": "^14.2.1", diff --git a/extension/src/popup/assets/icon-new-asset.svg b/extension/src/popup/assets/icon-new-asset.svg new file mode 100644 index 0000000000..59985c291e --- /dev/null +++ b/extension/src/popup/assets/icon-new-asset.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/extension/src/popup/assets/icon-unverified.svg b/extension/src/popup/assets/icon-unverified.svg index a5bbfca83d..883c5cc4c8 100644 --- a/extension/src/popup/assets/icon-unverified.svg +++ b/extension/src/popup/assets/icon-unverified.svg @@ -1,5 +1,5 @@ - + - + diff --git a/extension/src/popup/components/AssetNotification/index.tsx b/extension/src/popup/components/AssetNotification/index.tsx new file mode 100644 index 0000000000..680d286347 --- /dev/null +++ b/extension/src/popup/components/AssetNotification/index.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Tooltip, Icon } from "@stellar/design-system"; + +import "./styles.scss"; + +export const AssetNotifcation = ({ isVerified }: { isVerified: boolean }) => { + const { t } = useTranslation(); + + return ( +
+ {isVerified ? t("On your lists") : t("Not on your lists")} + + + + } + > + {t( + "Freighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings.", + )} + +
+ ); +}; diff --git a/extension/src/popup/components/AssetNotification/styles.scss b/extension/src/popup/components/AssetNotification/styles.scss new file mode 100644 index 0000000000..b7de195e47 --- /dev/null +++ b/extension/src/popup/components/AssetNotification/styles.scss @@ -0,0 +1,24 @@ +.AssetNotification { + align-items: center; + color: var(--color-gray-70); + display: flex; + font-size: 0.875rem; + font-weight: var(--font-weight-medium); + gap: 0.25rem; + line-height: 1.25rem; + margin-bottom: 1rem; + + &__button { + border: none; + background: none; + display: flex; + text-decoration: none; + cursor: pointer; + padding: 0; + } + + &__info { + height: 0.875rem; + width: 0.875rem; + } +} diff --git a/extension/src/popup/components/WarningMessages/index.tsx b/extension/src/popup/components/WarningMessages/index.tsx index df537310fb..f1cc9ec21e 100644 --- a/extension/src/popup/components/WarningMessages/index.tsx +++ b/extension/src/popup/components/WarningMessages/index.tsx @@ -1,13 +1,7 @@ import React, { useState, useRef, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { createPortal } from "react-dom"; -import { - Button, - Icon, - Loader, - Link, - Notification, -} from "@stellar/design-system"; +import { Button, Icon, Loader, Notification } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; import { POPUP_HEIGHT } from "constants/dimensions"; import { @@ -19,6 +13,7 @@ import { Networks, xdr, } from "stellar-sdk"; +import { captureException } from "@sentry/browser"; import { ActionStatus } from "@shared/api/types"; import { getTokenDetails } from "@shared/api/internal"; @@ -57,8 +52,12 @@ import { emitMetric } from "helpers/metrics"; import IconShieldCross from "popup/assets/icon-shield-cross.svg"; import IconInvalid from "popup/assets/icon-invalid.svg"; import IconWarning from "popup/assets/icon-warning.svg"; -import { searchToken } from "popup/helpers/searchAsset"; -import { captureException } from "@sentry/browser"; +import IconUnverified from "popup/assets/icon-unverified.svg"; +import IconNewAsset from "popup/assets/icon-new-asset.svg"; +import { + getVerifiedTokens, + VerifiedTokenRecord, +} from "popup/helpers/searchAsset"; import { CopyValue } from "../CopyValue"; import "./styles.scss"; @@ -689,16 +688,20 @@ export const NewAssetWarning = ({ ); }; -export const UnverifiedTokenWarning = ({ +export const TokenWarning = ({ domain, code, issuer, onClose, + isVerifiedToken, + verifiedLists = [], }: { domain: string; code: string; issuer: string; onClose: () => void; + isVerifiedToken: boolean; + verifiedLists?: string[]; }) => { const { t } = useTranslation(); const dispatch: AppDispatch = useDispatch(); @@ -746,70 +749,78 @@ export const UnverifiedTokenWarning = ({ }; return ( -
+
-
-
-
+
+
+
-
{code}
-
{domain}
-
-
+
{code}
+
{domain}
+
+
-
+
{t("Add Asset Trustline")}
+ {isVerifiedToken ? ( + + {t( + "Freighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings.", + )} + + ) : ( + + )} + +
+
{t("Asset Info")}
- +
+ unverified icon +
+
+
+ {t("Unverified asset")} +
+
+ {t("Proceed with caution")} +
+
+
)} - variant="warning" - /> -
-
- {t("Asset Info")} -
-
-
- +
+
+ new asset icon
-
-
- {t( - "The asset is not part of Stellar Expert's top 50 assets list", - )} +
+
+ {t("New asset")}
-
- {t("This asset is not part of")}{" "} - - Stellar Expert's top 50 assets list - -
- - {t("Learn more")} - +
+ {t("This is a relatively new asset")}
-
+
) : null} {assetRows.length && isVerificationInfoShowing ? ( - + ) : null} {assetRows.length ? ( ) : null} {hasNoResults && dirty && !isSearching ? ( diff --git a/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx b/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx index 3e81881eab..a965b9b72e 100644 --- a/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx +++ b/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx @@ -44,7 +44,7 @@ import { HardwareSign } from "popup/components/hardwareConnect/HardwareSign"; import { ScamAssetWarning, NewAssetWarning, - UnverifiedTokenWarning, + TokenWarning, } from "popup/components/WarningMessages"; import { ScamAssetIcon } from "popup/components/account/ScamAssetIcon"; @@ -72,6 +72,16 @@ interface ManageAssetRowsProps { assetRows: ManageAssetCurrency[]; chooseAsset?: boolean; isVerifiedToken?: boolean; + isVerificationInfoShowing?: boolean; + verifiedLists?: string[]; +} + +interface SuspiciousAssetData { + domain: string; + code: string; + issuer: string; + image: string; + isVerifiedToken?: boolean; } export const ManageAssetRows = ({ @@ -80,6 +90,8 @@ export const ManageAssetRows = ({ assetRows, chooseAsset, isVerifiedToken, + isVerificationInfoShowing, + verifiedLists, }: ManageAssetRowsProps) => { const { t } = useTranslation(); const publicKey = useSelector(publicKeySelector); @@ -112,7 +124,8 @@ export const ManageAssetRows = ({ code: "", issuer: "", image: "", - }); + isVerifiedToken: false, + } as SuspiciousAssetData); const server = stellarSdkServer(networkDetails.networkUrl); @@ -259,7 +272,16 @@ export const ManageAssetRows = ({ const contractId = assetRowData.issuer; setAssetSubmitting(canonicalAsset || contractId); if (!isTrustlineActive) { - if (isVerifiedToken) { + if (isVerificationInfoShowing) { + setSuspiciousAssetData({ + domain: assetRowData.domain, + code: assetRowData.code, + issuer: assetRowData.issuer, + image: assetRowData.image, + isVerifiedToken: !!isVerifiedToken, + }); + setShowUnverifiedWarning(true); + } else { await dispatch( addTokenId({ publicKey, @@ -268,14 +290,6 @@ export const ManageAssetRows = ({ }), ); navigateTo(ROUTES.account); - } else { - setSuspiciousAssetData({ - domain: assetRowData.domain, - code: assetRowData.code, - issuer: assetRowData.issuer, - image: assetRowData.image, - }); - setShowUnverifiedWarning(true); } } else { await dispatch( @@ -317,13 +331,15 @@ export const ManageAssetRows = ({ /> )} {showUnverifiedWarning && ( - { setShowUnverifiedWarning(false); }} + isVerifiedToken={!!suspiciousAssetData.isVerifiedToken} + verifiedLists={verifiedLists} /> )}
diff --git a/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx b/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx index 8b1f6703c9..3f285d0440 100644 --- a/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx +++ b/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx @@ -17,7 +17,7 @@ import { isContractId } from "popup/helpers/soroban"; import { useIsSwap } from "popup/helpers/useIsSwap"; import { useIsOwnedScamAsset } from "popup/helpers/useIsOwnedScamAsset"; import { ScamAssetIcon } from "popup/components/account/ScamAssetIcon"; -import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; +import { settingsSelector } from "popup/ducks/settings"; import { getVerifiedTokens } from "popup/helpers/searchAsset"; import "./styles.scss"; @@ -32,7 +32,7 @@ export const AssetSelect = ({ const { t } = useTranslation(); const dispatch = useDispatch(); const { assetIcons } = useSelector(transactionSubmissionSelector); - const networkDetails = useSelector(settingsNetworkDetailsSelector); + const { networkDetails, assetsLists } = useSelector(settingsSelector); const isOwnedScamAsset = useIsOwnedScamAsset(assetCode, issuerKey); const [isUnverifiedToken, setIsUnverifiedToken] = useState(false); @@ -49,6 +49,7 @@ export const AssetSelect = ({ const verifiedTokens = await getVerifiedTokens({ networkDetails, contractId: issuerKey, + assetsLists, }); if (!verifiedTokens.length) { @@ -57,7 +58,7 @@ export const AssetSelect = ({ }; fetchVerifiedTokens(); - }, [issuerKey, networkDetails]); + }, [issuerKey, networkDetails, assetsLists]); const handleSelectAsset = () => { dispatch(saveAssetSelectType(AssetSelectType.REGULAR)); diff --git a/extension/src/popup/helpers/searchAsset.ts b/extension/src/popup/helpers/searchAsset.ts index a60e6fa9db..6b52f73220 100644 --- a/extension/src/popup/helpers/searchAsset.ts +++ b/extension/src/popup/helpers/searchAsset.ts @@ -1,6 +1,6 @@ import { captureException } from "@sentry/browser"; -import { fetchAssetList } from "@stellar-asset-lists/sdk"; import { NetworkDetails, NETWORKS } from "@shared/constants/stellar"; +import { AssetsLists, AssetsListKey } from "@shared/constants/soroban/token"; import { getApiStellarExpertUrl } from "popup/helpers/account"; export const searchAsset = async ({ @@ -48,34 +48,6 @@ export const getNativeContractDetails = (networkDetails: NetworkDetails) => { } }; -export const searchTokenUrl = (networkDetails: NetworkDetails) => - `${getApiStellarExpertUrl(networkDetails)}/asset-list/top50`; - -export const searchToken = async ({ - networkDetails, - onError, -}: { - networkDetails: NetworkDetails; - onError: (e: any) => void; -}) => { - let verifiedAssets = [] as TokenRecord[]; - try { - const res: { assets: TokenRecord[] } = await fetchAssetList( - searchTokenUrl(networkDetails), - ); - verifiedAssets = verifiedAssets.concat(res.assets); - } catch (e) { - onError(e); - } - - // add native contract to list - verifiedAssets = verifiedAssets.concat([ - getNativeContractDetails(networkDetails), - ]); - - return verifiedAssets; -}; - export interface TokenRecord { code: string; issuer: string; @@ -86,39 +58,77 @@ export interface TokenRecord { decimals: number; } +export type VerifiedTokenRecord = TokenRecord & { verifiedLists: string[] }; + export const getVerifiedTokens = async ({ networkDetails, contractId, setIsSearching, + assetsLists, }: { networkDetails: NetworkDetails; contractId: string; setIsSearching?: (isSearching: boolean) => void; + assetsLists: AssetsLists; }) => { - let verifiedTokens = [] as TokenRecord[]; - - const fetchVerifiedTokens = async () => { - const verifiedTokenRes = await searchToken({ - networkDetails, - onError: (e) => { - console.error(e); - if (setIsSearching) { - setIsSearching(false); - captureException("Unable to search stellar.expert token list"); - } - }, - }); + const networkLists = assetsLists[networkDetails.network as AssetsListKey]; + const promiseArr = []; + const nativeContract = getNativeContractDetails(networkDetails); + + if (contractId === nativeContract.contract) { + return [{ ...nativeContract, verifiedLists: [] }]; + } + + // eslint-disable-next-line no-restricted-syntax + for (const networkList of networkLists) { + const { url = "" } = networkList; + + const fetchAndParse = async () => { + let res; + try { + res = await fetch(url); + } catch (e) { + captureException(`Failed to load asset list: ${url}`); + } + + return res?.json(); + }; + + promiseArr.push(fetchAndParse()); + } + + const promiseRes = await Promise.allSettled(promiseArr); + + const verifiedTokens = [] as VerifiedTokenRecord[]; + + let verifiedToken = {} as TokenRecord; + const verifiedLists: string[] = []; - verifiedTokens = verifiedTokenRes.filter((record: TokenRecord) => { - const regex = new RegExp(contractId, "i"); - if (record.contract.match(regex)) { - return true; + promiseRes.forEach((r) => { + if (r.status === "fulfilled") { + const list = r.value?.tokens ? r.value?.tokens : r.value?.assets; + if (list) { + list.forEach((record: TokenRecord) => { + const regex = new RegExp(contractId, "i"); + if (record.contract && record.contract.match(regex)) { + verifiedToken = record; + verifiedLists.push(r.value.name as string); + } + }); } - return false; + } + }); + + if (Object.keys(verifiedToken).length) { + verifiedTokens.push({ + ...verifiedToken, + verifiedLists, }); - }; + } - await fetchVerifiedTokens(); + if (setIsSearching) { + setIsSearching(false); + } return verifiedTokens; }; diff --git a/extension/src/popup/locales/en/translation.json b/extension/src/popup/locales/en/translation.json index 3a06552c8b..6e2868e938 100644 --- a/extension/src/popup/locales/en/translation.json +++ b/extension/src/popup/locales/en/translation.json @@ -80,9 +80,6 @@ "Balance ID": "Balance ID", "Before we start with migration, please read": "Before we start with migration, please read", "Before You Add This Asset": "Before You Add This Asset", - "Before you add this asset, please double-check its information and characteristics": { - " This can help you identify fraudulent assets": "Before you add this asset, please double-check its information and characteristics. This can help you identify fraudulent assets." - }, "Block malicious or unsafe addresses and domains": "Block malicious or unsafe addresses and domains", "Block trustlines to malicious or fraudulent assets": "Block trustlines to malicious or fraudulent assets", "Blocked asset": "Blocked asset", @@ -181,6 +178,9 @@ " It’s a safer alternative to copying and pasting private keys for use with web apps": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser. It’s a safer alternative to copying and pasting private keys for use with web apps." }, "Freighter is set to": "Freighter is set to", + "Freighter uses asset lists to check assets you interact with": { + " You can define your own assets lists in Settings": "Freighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings." + }, "Freighter will never ask for your recovery phrase unless you’re actively importing your account using the browser extension - never on an external website": "Freighter will never ask for your recovery phrase unless you’re actively importing your account using the browser extension - never on an external website.", "Freighter will use experimental API‘s and connect to the Futurenet, a test network": { " Please proceed at your own risk as you may be interacting with schemas that are untested and still changing": "Freighter will use experimental API‘s and connect to the Futurenet, a test network. Please proceed at your own risk as you may be interacting with schemas that are untested and still changing." @@ -300,6 +300,7 @@ "Never disclose your recovery phrase": "Never disclose your recovery phrase", "Nevermind, cancel": "Nevermind, cancel", "New account": "New account", + "New asset": "New asset", "New Asset": "New Asset", "New password": "New password", "Next": "Next", @@ -309,9 +310,11 @@ "Not enough lumens": "Not enough lumens", "Not funded": "Not funded", "Not migrated": "Not migrated", + "Not on your lists": "Not on your lists", "Not recommended asset": "Not recommended asset", "Now, let’s create a new mnemonic phrase": "Now, let’s create a new mnemonic phrase", "Offer ID": "Offer ID", + "On your lists": "On your lists", "One of your accounts is a signer for another account": { " Freighter won’t migrate signing settings": { " For your safety, Freighter won’t merge accounts with signature set up so you can still control it": "One of your accounts is a signer for another account. Freighter won’t migrate signing settings. For your safety, Freighter won’t merge accounts with signature set up so you can still control it." @@ -343,6 +346,7 @@ "Preferences": "Preferences", "Price": "Price", "Privacy Policy": "Privacy Policy", + "Proceed with caution": "Proceed with caution", "Processing": "Processing", "Projects related to this asset may be fraudulent even if the creators say otherwise": { " ": "Projects related to this asset may be fraudulent even if the creators say otherwise. " @@ -426,7 +430,6 @@ "Terms of Use": "Terms of Use", "The application is requesting a specific account": "The application is requesting a specific account", "The asset creator can revoke your access to this asset at anytime": "The asset creator can revoke your access to this asset at anytime", - "The asset is not part of Stellar Expert's top 50 assets list": "The asset is not part of Stellar Expert's top 50 assets list", "The destination account can receive a different asset, the received amount is defined by the available conversion rates": "The destination account can receive a different asset, the received amount is defined by the available conversion rates", "The destination account does not accept the asset you’re sending": "The destination account does not accept the asset you’re sending", "The destination account doesn’t exist": "The destination account doesn’t exist.", @@ -445,7 +448,14 @@ "This asset has a balance": "This asset has a balance", "This asset has a balance of": "This asset has a balance of", "This asset is not part of": "This asset is not part of", - "This asset is part of": "This asset is part of", + "This asset is not part of an asset list": { + " Please, double-check the asset you're interacting with and proceed with care": { + " Freighter uses asset lists to check assets you interact with": { + " You can define your own assets lists in Settings": "This asset is not part of an asset list. Please, double-check the asset you're interacting with and proceed with care. Freighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings." + } + } + }, + "This asset is part of the asset lists": "This asset is part of the asset lists", "This asset was tagged as fraudulent by stellar": { "expert, a reliable community-maintained directory": "This asset was tagged as fraudulent by stellar.expert, a reliable community-maintained directory." }, @@ -477,6 +487,7 @@ "Unable to search for assets": "Unable to search for assets", "Unsafe": "Unsafe", "Unsupported signing method": "Unsupported signing method", + "Unverified asset": "Unverified asset", "Validate addresses that require a memo": "Validate addresses that require a memo", "Value": "Value", "Verification": "Verification", diff --git a/extension/src/popup/locales/pt/translation.json b/extension/src/popup/locales/pt/translation.json index 7e926ee0f9..d81aa15984 100644 --- a/extension/src/popup/locales/pt/translation.json +++ b/extension/src/popup/locales/pt/translation.json @@ -80,9 +80,6 @@ "Balance ID": "Balance ID", "Before we start with migration, please read": "Before we start with migration, please read", "Before You Add This Asset": "Before You Add This Asset", - "Before you add this asset, please double-check its information and characteristics": { - " This can help you identify fraudulent assets": "Before you add this asset, please double-check its information and characteristics. This can help you identify fraudulent assets." - }, "Block malicious or unsafe addresses and domains": "Block malicious or unsafe addresses and domains", "Block trustlines to malicious or fraudulent assets": "Block trustlines to malicious or fraudulent assets", "Blocked asset": "Blocked asset", @@ -181,6 +178,9 @@ " It’s a safer alternative to copying and pasting private keys for use with web apps": "Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser. It’s a safer alternative to copying and pasting private keys for use with web apps." }, "Freighter is set to": "Freighter is set to", + "Freighter uses asset lists to check assets you interact with": { + " You can define your own assets lists in Settings": "Freighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings." + }, "Freighter will never ask for your recovery phrase unless you’re actively importing your account using the browser extension - never on an external website": "Freighter will never ask for your recovery phrase unless you’re actively importing your account using the browser extension - never on an external website.", "Freighter will use experimental API‘s and connect to the Futurenet, a test network": { " Please proceed at your own risk as you may be interacting with schemas that are untested and still changing": "Freighter will use experimental API‘s and connect to the Futurenet, a test network. Please proceed at your own risk as you may be interacting with schemas that are untested and still changing." @@ -300,6 +300,7 @@ "Never disclose your recovery phrase": "Never disclose your recovery phrase", "Nevermind, cancel": "Nevermind, cancel", "New account": "New account", + "New asset": "New asset", "New Asset": "New Asset", "New password": "New password", "Next": "Next", @@ -309,9 +310,11 @@ "Not enough lumens": "Not enough lumens", "Not funded": "Not funded", "Not migrated": "Not migrated", + "Not on your lists": "Not on your lists", "Not recommended asset": "Not recommended asset", "Now, let’s create a new mnemonic phrase": "Now, let’s create a new mnemonic phrase", "Offer ID": "Offer ID", + "On your lists": "On your lists", "One of your accounts is a signer for another account": { " Freighter won’t migrate signing settings": { " For your safety, Freighter won’t merge accounts with signature set up so you can still control it": "One of your accounts is a signer for another account. Freighter won’t migrate signing settings. For your safety, Freighter won’t merge accounts with signature set up so you can still control it." @@ -343,6 +346,7 @@ "Preferences": "Preferences", "Price": "Price", "Privacy Policy": "Privacy Policy", + "Proceed with caution": "Proceed with caution", "Processing": "Processing", "Projects related to this asset may be fraudulent even if the creators say otherwise": { " ": "Projects related to this asset may be fraudulent even if the creators say otherwise. " @@ -426,7 +430,6 @@ "Terms of Use": "Terms of Use", "The application is requesting a specific account": "The application is requesting a specific account", "The asset creator can revoke your access to this asset at anytime": "The asset creator can revoke your access to this asset at anytime", - "The asset is not part of Stellar Expert's top 50 assets list": "The asset is not part of Stellar Expert's top 50 assets list", "The destination account can receive a different asset, the received amount is defined by the available conversion rates": "The destination account can receive a different asset, the received amount is defined by the available conversion rates", "The destination account does not accept the asset you’re sending": "The destination account does not accept the asset you’re sending", "The destination account doesn’t exist": "The destination account doesn’t exist.", @@ -445,7 +448,14 @@ "This asset has a balance": "This asset has a balance", "This asset has a balance of": "This asset has a balance of", "This asset is not part of": "This asset is not part of", - "This asset is part of": "This asset is part of", + "This asset is not part of an asset list": { + " Please, double-check the asset you're interacting with and proceed with care": { + " Freighter uses asset lists to check assets you interact with": { + " You can define your own assets lists in Settings": "This asset is not part of an asset list. Please, double-check the asset you're interacting with and proceed with care. Freighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings." + } + } + }, + "This asset is part of the asset lists": "This asset is part of the asset lists", "This asset was tagged as fraudulent by stellar": { "expert, a reliable community-maintained directory": "This asset was tagged as fraudulent by stellar.expert, a reliable community-maintained directory." }, @@ -477,6 +487,7 @@ "Unable to search for assets": "Unable to search for assets", "Unsafe": "Unsafe", "Unsupported signing method": "Unsupported signing method", + "Unverified asset": "Unverified asset", "Validate addresses that require a memo": "Validate addresses that require a memo", "Value": "Value", "Verification": "Verification", diff --git a/extension/src/popup/views/__tests__/ManageAssets.test.tsx b/extension/src/popup/views/__tests__/ManageAssets.test.tsx index abc64921c4..b40d4be8b4 100644 --- a/extension/src/popup/views/__tests__/ManageAssets.test.tsx +++ b/extension/src/popup/views/__tests__/ManageAssets.test.tsx @@ -37,6 +37,8 @@ const mockXDR = "AAAAAgAAAADaBSz5rQFDZHNdV8//w/Yiy11vE1ZxGJ8QD8j7HUtNEwAAAGQAAAAAAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAADaBSz5rQFDZHNdV8//w/Yiy11vE1ZxGJ8QD8j7HUtNEwAAAAAAAAAAAvrwgAAAAAAAAAABHUtNEwAAAEBY/jSiXJNsA2NpiXrOi6Ll6RiIY7v8QZEEZviM8HmmzeI4FBP9wGZm7YMorQue+DK9KI5BEXDt3hi0VOA9gD8A"; const verifiedToken = "CBIELTK6YBZJU5UP2WWQEUCYKLPU6AUNZ2BQ4WWFEIE3USCIHMXQDAMA"; +const unverifiedToken = + "CAZXRTOKNUQ2JQQF3NCRU7GYMDJNZ2NMQN6IGN4FCT5DWPODMPVEXSND"; const manageAssetsMockBalances = { balances: ({ @@ -171,6 +173,7 @@ jest icon: "", issuer: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5", org: "unknown", + verifiedLists: [], }, ]); } @@ -178,9 +181,18 @@ jest return Promise.resolve([]); }); +jest + .spyOn(ApiInternal, "getTokenDetails") + .mockImplementation(() => + Promise.resolve({ name: "foo", symbol: "baz", decimals: 7 }), + ); + jest .spyOn(SorobanHelpers, "isContractId") - .mockImplementation((contractId) => contractId === verifiedToken); + .mockImplementation( + (contractId) => + contractId === verifiedToken || contractId === unverifiedToken, + ); const mockHistoryGetter = jest.fn(); jest.mock("popup/constants/history", () => ({ @@ -468,7 +480,7 @@ describe("Manage assets", () => { const lastRoute = history.entries.pop(); expect(lastRoute?.pathname).toBe("/account"); }); - it("add soroban token", async () => { + it("add soroban token on asset list", async () => { // init Mainnet view await initView(false, true); @@ -494,14 +506,10 @@ describe("Manage assets", () => { }); await waitFor(async () => { const addedTrustlines = screen.queryAllByTestId("ManageAssetRow"); - const verificationBadge = screen.getByTestId("add-token-verification"); + const verificationBadge = screen.getByTestId("asset-notification"); expect(verificationBadge).toHaveTextContent( - "This asset is part of Stellar Expert's top 50 assets list. Learn more", - ); - expect(screen.getByTestId("add-token-verification-url")).toHaveAttribute( - "href", - "https://api.stellar.expert/explorer/public/asset-list/top50", + "On your listsFreighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings.", ); expect(addedTrustlines.length).toBe(1); @@ -516,6 +524,55 @@ describe("Manage assets", () => { "ManageAssetRowButton", ); + expect(addAssetButton).toHaveTextContent("Add"); + expect(addAssetButton).toBeEnabled(); + await fireEvent.click(addAssetButton); + }); + }); + it("add soroban token not on asset list", async () => { + // init Mainnet view + await initView(false, true); + + const addTokenButton = screen.getByTestId( + "ChooseAssetAddSorobanTokenButton", + ); + expect(addTokenButton).toBeEnabled(); + await fireEvent.click(addTokenButton); + + await waitFor(() => { + screen.getByTestId("AppHeaderPageTitle"); + expect(screen.getByTestId("AppHeaderPageTitle")).toHaveTextContent( + "Add a Soroban token by ID", + ); + + const searchInput = screen.getByTestId("search-token-input"); + fireEvent.change(searchInput, { + target: { + value: unverifiedToken, + }, + }); + expect(searchInput).toHaveValue(unverifiedToken); + }); + await waitFor(async () => { + const addedTrustlines = screen.queryAllByTestId("ManageAssetRow"); + const verificationBadge = screen.getByTestId("asset-notification"); + + expect(verificationBadge).toHaveTextContent( + "Not on your listsFreighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings.", + ); + + expect(addedTrustlines.length).toBe(1); + expect( + within(addedTrustlines[0]).getByTestId("ManageAssetCode"), + ).toHaveTextContent("foo"); + expect( + within(addedTrustlines[0]).getByTestId("ManageAssetDomain"), + ).toHaveTextContent("Stellar Network"); + + const addAssetButton = within(addedTrustlines[0]).getByTestId( + "ManageAssetRowButton", + ); + expect(addAssetButton).toHaveTextContent("Add"); expect(addAssetButton).toBeEnabled(); await fireEvent.click(addAssetButton); diff --git a/yarn.lock b/yarn.lock index 4ec3df4b14..1ee7869ecd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4831,11 +4831,6 @@ p-map "^4.0.0" webpack-sources "^3.2.2" -"@stellar-asset-lists/sdk@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@stellar-asset-lists/sdk/-/sdk-1.0.0.tgz#59a70b07c01247ffed2f8d370d624ca2650c0b50" - integrity sha512-2RBdkUVStRbZZG7yxL6ow3B6cCjClSHe5bHF/NGKAkKR6MDXEfIvoLndsl4uWoLJTLfe3vdL9UX/T14p4INdBA== - "@stellar/design-system@^1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@stellar/design-system/-/design-system-1.1.2.tgz#09f27c13fef69975c6d9a214e770e419985c0b8e"