From a33d7dc8307c86ea9f16b42c5c3e54a2a0b27800 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Wed, 27 Mar 2024 15:36:39 -0400 Subject: [PATCH 1/9] first pass at adding multiple asset lists --- .../components/WarningMessages/index.tsx | 31 +++-- .../manageAssets/AddToken/index.tsx | 27 ++--- .../SendAmount/AssetSelect/index.tsx | 7 +- .../SendConfirm/TransactionDetails/index.tsx | 1 + extension/src/popup/helpers/searchAsset.ts | 111 ++++++++++-------- .../views/__tests__/ManageAssets.test.tsx | 1 + 6 files changed, 94 insertions(+), 84 deletions(-) diff --git a/extension/src/popup/components/WarningMessages/index.tsx b/extension/src/popup/components/WarningMessages/index.tsx index df537310fb..92c66835fd 100644 --- a/extension/src/popup/components/WarningMessages/index.tsx +++ b/extension/src/popup/components/WarningMessages/index.tsx @@ -57,7 +57,10 @@ 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 { + getVerifiedTokens, + VerifiedTokenRecord, +} from "popup/helpers/searchAsset"; import { captureException } from "@sentry/browser"; import { CopyValue } from "../CopyValue"; @@ -903,8 +906,9 @@ export const UnverifiedTokenTransferWarning = ({ }: { details: { contractId: string }[]; }) => { + console.log(1); const { t } = useTranslation(); - const networkDetails = useSelector(settingsNetworkDetailsSelector); + const { networkDetails, assetsLists } = useSelector(settingsSelector); const [isUnverifiedToken, setIsUnverifiedToken] = useState(false); useEffect(() => { @@ -912,21 +916,16 @@ export const UnverifiedTokenTransferWarning = ({ return; } const fetchVerifiedTokens = async () => { - const verifiedTokenRes = await searchToken({ - networkDetails, - onError: (e) => console.error(e), - }); - const verifiedTokens = [] as string[]; + let verifiedTokens = [] as VerifiedTokenRecord[]; // eslint-disable-next-line - for (let i = 0; i < verifiedTokenRes.length; i += 1) { - // eslint-disable-next-line - for (let j = 0; j < details.length; j += 1) { - if (details[j].contractId === verifiedTokenRes[i].contract) { - verifiedTokens.push(details[j].contractId); - return; - } - } + for (let j = 0; j < details.length; j += 1) { + const c = details[j].contractId; + verifiedTokens = await getVerifiedTokens({ + contractId: c, + networkDetails, + assetsLists, + }); } if (!verifiedTokens.length) { @@ -935,7 +934,7 @@ export const UnverifiedTokenTransferWarning = ({ }; fetchVerifiedTokens(); - }, [networkDetails, details]); + }, [networkDetails, details, assetsLists]); return isUnverifiedToken ? ( { +const VerificationBadge = ({ isVerified }: { isVerified: boolean }) => { const { t } = useTranslation(); - const linkUrl = searchTokenUrl(networkDetails); + const linkUrl = ""; return (
@@ -109,6 +104,8 @@ export const AddToken = () => { const [isVerificationInfoShowing, setIsVerificationInfoShowing] = useState( false, ); + const { assetsLists } = useSelector(settingsSelector); + const ResultsRef = useRef(null); const isAllowListVerificationEnabled = isMainnet(networkDetails) || isTestnet(networkDetails); @@ -180,8 +177,11 @@ export const AddToken = () => { networkDetails, contractId, setIsSearching, + assetsLists, }); + console.log(verifiedTokens); + try { if (verifiedTokens.length) { setIsVerifiedToken(true); @@ -279,10 +279,7 @@ export const AddToken = () => {
) : null} {assetRows.length && isVerificationInfoShowing ? ( - + ) : null} {assetRows.length ? ( 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/components/sendPayment/SendConfirm/TransactionDetails/index.tsx b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx index bce466b34d..27b2298885 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx +++ b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx @@ -269,6 +269,7 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { }, [dispatch]); const handleXferTransaction = async () => { + console.log(transactionSimulation.preparedTransaction); try { const res = await dispatch( signFreighterSorobanTransaction({ diff --git a/extension/src/popup/helpers/searchAsset.ts b/extension/src/popup/helpers/searchAsset.ts index f4b1541ab3..a90a8d4bc6 100644 --- a/extension/src/popup/helpers/searchAsset.ts +++ b/extension/src/popup/helpers/searchAsset.ts @@ -1,5 +1,6 @@ -import { fetchAssetList } from "@stellar-asset-lists/sdk"; +import { captureException } from "@sentry/browser"; 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 ({ @@ -47,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; @@ -85,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); - } - throw new Error("Unable to search for tokens"); - }, - }); + const networkLists = assetsLists[networkDetails.network as AssetsListKey]; + const promiseArr = []; - verifiedTokens = verifiedTokenRes.filter((record: TokenRecord) => { - const regex = new RegExp(contractId, "i"); - if (record.contract.match(regex)) { - return true; + 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 false; - }); - }; - await fetchVerifiedTokens(); + return res?.json(); + }; + + promiseArr.push(fetchAndParse()); + } + + const promiseRes = await Promise.allSettled(promiseArr); + if (setIsSearching) { + setIsSearching(false); + } + + const verifiedTokens = [] as VerifiedTokenRecord[]; + + let verifiedToken = {} as TokenRecord; + const verifiedLists: string[] = []; + + 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); + } + }); + } + } + }); + + if (Object.keys(verifiedToken).length) { + verifiedTokens.push({ + ...verifiedToken, + verifiedLists, + }); + } return verifiedTokens; }; diff --git a/extension/src/popup/views/__tests__/ManageAssets.test.tsx b/extension/src/popup/views/__tests__/ManageAssets.test.tsx index abc64921c4..30a6a000e2 100644 --- a/extension/src/popup/views/__tests__/ManageAssets.test.tsx +++ b/extension/src/popup/views/__tests__/ManageAssets.test.tsx @@ -171,6 +171,7 @@ jest icon: "", issuer: "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5", org: "unknown", + verifiedLists: [], }, ]); } From cc9820718f4a9bae460050f10a0ac1b413919c48 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Thu, 28 Mar 2024 15:51:44 -0400 Subject: [PATCH 2/9] add verification badge --- .../components/WarningMessages/index.tsx | 6 ++- .../manageAssets/AddToken/index.tsx | 37 +++++++++---------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/extension/src/popup/components/WarningMessages/index.tsx b/extension/src/popup/components/WarningMessages/index.tsx index 92c66835fd..64e9553efb 100644 --- a/extension/src/popup/components/WarningMessages/index.tsx +++ b/extension/src/popup/components/WarningMessages/index.tsx @@ -928,6 +928,8 @@ export const UnverifiedTokenTransferWarning = ({ }); } + console.log(verifiedTokens); + if (!verifiedTokens.length) { setIsUnverifiedToken(true); } @@ -938,13 +940,13 @@ export const UnverifiedTokenTransferWarning = ({ return isUnverifiedToken ? (

{t( - `This asset is not part of the asset list by stellar.expert (${networkDetails.network})`, + `This asset is not part of any of your enabled asset lists (${networkDetails.network})`, )}

diff --git a/extension/src/popup/components/manageAssets/AddToken/index.tsx b/extension/src/popup/components/manageAssets/AddToken/index.tsx index 68d0d5031a..8b0d1d3427 100644 --- a/extension/src/popup/components/manageAssets/AddToken/index.tsx +++ b/extension/src/popup/components/manageAssets/AddToken/index.tsx @@ -24,8 +24,8 @@ import { import { isMainnet, isTestnet } from "helpers/stellar"; import { getVerifiedTokens, - TokenRecord, getNativeContractDetails, + VerifiedTokenRecord, } from "popup/helpers/searchAsset"; import { isContractId } from "popup/helpers/soroban"; @@ -43,7 +43,13 @@ const initialValues: FormValues = { asset: "", }; -const VerificationBadge = ({ isVerified }: { isVerified: boolean }) => { +const VerificationBadge = ({ + isVerified, + verifiedLists, +}: { + isVerified: boolean; + verifiedLists: string[]; +}) => { const { t } = useTranslation(); const linkUrl = ""; @@ -53,20 +59,8 @@ const VerificationBadge = ({ isVerified }: { isVerified: boolean }) => { <> - {t("This asset is part of")}{" "} - - Stellar Expert's top 50 assets list - - .{" "} - - {t("Learn more")} - + {t("This asset is part of the asset lists")}{" "} + {verifiedLists.join(", ")} ) : ( @@ -104,6 +98,7 @@ export const AddToken = () => { const [isVerificationInfoShowing, setIsVerificationInfoShowing] = useState( false, ); + const [verifiedLists, setVerifiedLists] = useState([] as string[]); const { assetsLists } = useSelector(settingsSelector); const ResultsRef = useRef(null); @@ -125,7 +120,7 @@ export const AddToken = () => { setAssetRows([]); const nativeContractDetails = getNativeContractDetails(networkDetails); - let verifiedTokens = [] as TokenRecord[]; + let verifiedTokens = [] as VerifiedTokenRecord[]; // step around verification for native contract and unverifiable networks @@ -185,8 +180,9 @@ export const AddToken = () => { try { if (verifiedTokens.length) { setIsVerifiedToken(true); + setVerifiedLists(verifiedTokens[0].verifiedLists); setAssetRows( - verifiedTokens.map((record: TokenRecord) => ({ + verifiedTokens.map((record: VerifiedTokenRecord) => ({ code: record.code, issuer: record.contract, image: record.icon, @@ -279,7 +275,10 @@ export const AddToken = () => { ) : null} {assetRows.length && isVerificationInfoShowing ? ( - + ) : null} {assetRows.length ? ( From 08efaf23103442ad677243ba563a31aaac5c6494 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Fri, 29 Mar 2024 14:15:34 -0400 Subject: [PATCH 3/9] add asset notifcation tooltip --- .../components/AssetNotification/index.tsx | 27 +++++++++++++++++++ .../components/AssetNotification/styles.scss | 24 +++++++++++++++++ .../manageAssets/AddToken/index.tsx | 11 ++++---- 3 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 extension/src/popup/components/AssetNotification/index.tsx create mode 100644 extension/src/popup/components/AssetNotification/styles.scss diff --git a/extension/src/popup/components/AssetNotification/index.tsx b/extension/src/popup/components/AssetNotification/index.tsx new file mode 100644 index 0000000000..75c939ac7e --- /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/manageAssets/AddToken/index.tsx b/extension/src/popup/components/manageAssets/AddToken/index.tsx index 8b0d1d3427..1de233e954 100644 --- a/extension/src/popup/components/manageAssets/AddToken/index.tsx +++ b/extension/src/popup/components/manageAssets/AddToken/index.tsx @@ -29,6 +29,7 @@ import { } from "popup/helpers/searchAsset"; import { isContractId } from "popup/helpers/soroban"; +import { AssetNotifcation } from "popup/components/AssetNotification"; import { SubviewHeader } from "popup/components/SubviewHeader"; import { View } from "popup/basics/layout/View"; import IconUnverified from "popup/assets/icon-unverified.svg"; @@ -43,7 +44,7 @@ const initialValues: FormValues = { asset: "", }; -const VerificationBadge = ({ +export const VerificationBadge = ({ isVerified, verifiedLists, }: { @@ -176,6 +177,7 @@ export const AddToken = () => { }); console.log(verifiedTokens); + console.log(verifiedLists); try { if (verifiedTokens.length) { @@ -222,7 +224,7 @@ export const AddToken = () => { setIsVerificationInfoShowing(isAllowListVerificationEnabled); setIsSearching(false); - }, 500), + }, 250), [], ); @@ -275,10 +277,7 @@ export const AddToken = () => { ) : null} {assetRows.length && isVerificationInfoShowing ? ( - + ) : null} {assetRows.length ? ( From 49b348e151d0111451839386a4c0b579b5242927 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Fri, 29 Mar 2024 17:46:58 -0400 Subject: [PATCH 4/9] Added translations --- .../src/popup/locales/en/translation.json | 21 ++++++++++++++----- .../src/popup/locales/pt/translation.json | 21 ++++++++++++++----- 2 files changed, 32 insertions(+), 10 deletions(-) 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", From 65c6f5f1cf2504bdcb29b08fb2fa9734843fce3f Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Fri, 29 Mar 2024 17:48:14 -0400 Subject: [PATCH 5/9] rm consoles --- extension/src/popup/components/WarningMessages/index.tsx | 3 --- extension/src/popup/components/manageAssets/AddToken/index.tsx | 3 --- .../sendPayment/SendConfirm/TransactionDetails/index.tsx | 1 - 3 files changed, 7 deletions(-) diff --git a/extension/src/popup/components/WarningMessages/index.tsx b/extension/src/popup/components/WarningMessages/index.tsx index 388ef677f7..f1cc9ec21e 100644 --- a/extension/src/popup/components/WarningMessages/index.tsx +++ b/extension/src/popup/components/WarningMessages/index.tsx @@ -914,7 +914,6 @@ export const UnverifiedTokenTransferWarning = ({ }: { details: { contractId: string }[]; }) => { - console.log(1); const { t } = useTranslation(); const { networkDetails, assetsLists } = useSelector(settingsSelector); const [isUnverifiedToken, setIsUnverifiedToken] = useState(false); @@ -936,8 +935,6 @@ export const UnverifiedTokenTransferWarning = ({ }); } - console.log(verifiedTokens); - if (!verifiedTokens.length) { setIsUnverifiedToken(true); } diff --git a/extension/src/popup/components/manageAssets/AddToken/index.tsx b/extension/src/popup/components/manageAssets/AddToken/index.tsx index 9556c65317..e3071ea0d0 100644 --- a/extension/src/popup/components/manageAssets/AddToken/index.tsx +++ b/extension/src/popup/components/manageAssets/AddToken/index.tsx @@ -160,9 +160,6 @@ export const AddToken = () => { assetsLists, }); - console.log(verifiedTokens); - console.log(verifiedLists); - try { if (verifiedTokens.length) { setIsVerifiedToken(true); diff --git a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx index 27b2298885..bce466b34d 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx +++ b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx @@ -269,7 +269,6 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { }, [dispatch]); const handleXferTransaction = async () => { - console.log(transactionSimulation.preparedTransaction); try { const res = await dispatch( signFreighterSorobanTransaction({ From adc45496e00cd821ed995d4553dd2b3156713bac Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Fri, 29 Mar 2024 17:50:58 -0400 Subject: [PATCH 6/9] update yarn --- yarn.lock | 5 ----- 1 file changed, 5 deletions(-) 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" From 1cd9eac511dc15dbda8c62e4b479c74191ef83a5 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Fri, 29 Mar 2024 17:56:35 -0400 Subject: [PATCH 7/9] reset debounce param --- extension/src/popup/components/manageAssets/AddToken/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src/popup/components/manageAssets/AddToken/index.tsx b/extension/src/popup/components/manageAssets/AddToken/index.tsx index 08ba12fc70..3bb88a902e 100644 --- a/extension/src/popup/components/manageAssets/AddToken/index.tsx +++ b/extension/src/popup/components/manageAssets/AddToken/index.tsx @@ -187,7 +187,7 @@ export const AddToken = () => { setIsVerificationInfoShowing(isAllowListVerificationEnabled); setIsSearching(false); - }, 250), + }, 500), [], ); From acafccbfdd4434aeee7e7d55716473a6cbf7a793 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Fri, 29 Mar 2024 18:35:45 -0400 Subject: [PATCH 8/9] use getTokenDetails --- .../manageAssets/AddToken/index.tsx | 71 +++++-------------- .../manageAssets/ManageAssetRows/index.tsx | 24 +++---- 2 files changed, 31 insertions(+), 64 deletions(-) diff --git a/extension/src/popup/components/manageAssets/AddToken/index.tsx b/extension/src/popup/components/manageAssets/AddToken/index.tsx index 3bb88a902e..5de3a4aa14 100644 --- a/extension/src/popup/components/manageAssets/AddToken/index.tsx +++ b/extension/src/popup/components/manageAssets/AddToken/index.tsx @@ -6,13 +6,7 @@ import { Formik, Form, Field, FieldProps } from "formik"; import { Input, Loader } from "@stellar/design-system"; import debounce from "lodash/debounce"; import { useTranslation } from "react-i18next"; -import { INDEXER_URL } from "@shared/constants/mercury"; -import { getName, getSymbol } from "@shared/helpers/soroban/token"; -import { isCustomNetwork } from "@shared/helpers/stellar"; -import { - buildSorobanServer, - getNewTxBuilder, -} from "@shared/helpers/soroban/server"; +import { getTokenDetails } from "@shared/api/internal"; import { FormRows } from "popup/basics/Forms"; @@ -51,7 +45,6 @@ export const AddToken = () => { const [isSearching, setIsSearching] = useState(false); const [hasNoResults, setHasNoResults] = useState(false); const [isVerifiedToken, setIsVerifiedToken] = useState(false); - const [isNativeToken, setIsNativeToken] = useState(false); const [isVerificationInfoShowing, setIsVerificationInfoShowing] = useState( false, ); @@ -73,7 +66,6 @@ export const AddToken = () => { // clear the UI while we work through the flow setIsSearching(true); setIsVerifiedToken(false); - setIsNativeToken(false); setIsVerificationInfoShowing(false); setAssetRows([]); @@ -84,7 +76,7 @@ export const AddToken = () => { if (nativeContractDetails.contract === contractId) { // override our rules for verification for XLM - setIsNativeToken(true); + setIsVerificationInfoShowing(false); setAssetRows([ { code: nativeContractDetails.code, @@ -96,46 +88,23 @@ export const AddToken = () => { return; } - if (isCustomNetwork(networkDetails)) { - if (!networkDetails.sorobanRpcUrl) { - setAssetRows([]); - } else { - const server = buildSorobanServer(networkDetails.sorobanRpcUrl); - const name = await getName( - contractId, - server, - await getNewTxBuilder(publicKey, networkDetails, server), - ); - const symbol = await getSymbol( - contractId, - server, - await getNewTxBuilder(publicKey, networkDetails, server), - ); - - setAssetRows([ - { - code: symbol, - issuer: contractId, - domain: "", - name, - }, - ]); - } - setIsSearching(false); - return; - } - - const indexerLookup = async () => { + const tokenLookup = async () => { // lookup contract setIsVerifiedToken(false); - const tokenDetailsResponse = await getTokenDetails({ - contractId, - publicKey, - networkDetails, - }); + let tokenDetailsResponse; + + try { + tokenDetailsResponse = await getTokenDetails({ + contractId, + publicKey, + networkDetails, + }); + } catch (e) { + setAssetRows([]); + } if (!tokenDetailsResponse) { - throw new Error(JSON.stringify(contractId)); + setAssetRows([]); } else { setAssetRows([ { @@ -170,7 +139,7 @@ export const AddToken = () => { ); } else { // token not found on asset list, look up the details manually - await indexerLookup(); + await tokenLookup(); } } catch (e) { setAssetRows([]); @@ -181,7 +150,7 @@ export const AddToken = () => { } } else { // Futurenet token lookup - await indexerLookup(); + await tokenLookup(); } setIsVerificationInfoShowing(isAllowListVerificationEnabled); @@ -247,10 +216,8 @@ export const AddToken = () => { ) : null} diff --git a/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx b/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx index 4d3c0ceaed..a965b9b72e 100644 --- a/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx +++ b/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx @@ -72,7 +72,7 @@ interface ManageAssetRowsProps { assetRows: ManageAssetCurrency[]; chooseAsset?: boolean; isVerifiedToken?: boolean; - isNativeToken?: boolean; + isVerificationInfoShowing?: boolean; verifiedLists?: string[]; } @@ -90,7 +90,7 @@ export const ManageAssetRows = ({ assetRows, chooseAsset, isVerifiedToken, - isNativeToken, + isVerificationInfoShowing, verifiedLists, }: ManageAssetRowsProps) => { const { t } = useTranslation(); @@ -272,16 +272,7 @@ export const ManageAssetRows = ({ const contractId = assetRowData.issuer; setAssetSubmitting(canonicalAsset || contractId); if (!isTrustlineActive) { - if (isNativeToken) { - await dispatch( - addTokenId({ - publicKey, - tokenId: contractId, - network: networkDetails.network as Networks, - }), - ); - navigateTo(ROUTES.account); - } else { + if (isVerificationInfoShowing) { setSuspiciousAssetData({ domain: assetRowData.domain, code: assetRowData.code, @@ -290,6 +281,15 @@ export const ManageAssetRows = ({ isVerifiedToken: !!isVerifiedToken, }); setShowUnverifiedWarning(true); + } else { + await dispatch( + addTokenId({ + publicKey, + tokenId: contractId, + network: networkDetails.network as Networks, + }), + ); + navigateTo(ROUTES.account); } } else { await dispatch( From 86bd4c1409a19fbc856f049b81d22ea249505b20 Mon Sep 17 00:00:00 2001 From: Piyal Basu Date: Fri, 29 Mar 2024 20:21:57 -0400 Subject: [PATCH 9/9] fix tests --- .../components/AssetNotification/index.tsx | 2 +- .../views/__tests__/ManageAssets.test.tsx | 72 ++++++++++++++++--- 2 files changed, 65 insertions(+), 9 deletions(-) diff --git a/extension/src/popup/components/AssetNotification/index.tsx b/extension/src/popup/components/AssetNotification/index.tsx index 75c939ac7e..680d286347 100644 --- a/extension/src/popup/components/AssetNotification/index.tsx +++ b/extension/src/popup/components/AssetNotification/index.tsx @@ -8,7 +8,7 @@ export const AssetNotifcation = ({ isVerified }: { isVerified: boolean }) => { const { t } = useTranslation(); return ( -
+
{isVerified ? t("On your lists") : t("Not on your lists")} + 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", () => ({ @@ -469,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); @@ -495,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); @@ -517,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);