diff --git a/.eslintrc.js b/.eslintrc.js index ac4e5e29f..beab79c4f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -31,6 +31,15 @@ module.exports = { "react/jsx-filename-extension": ["error", { extensions: [".tsx", ".jsx"] }], "jsdoc/newline-after-description": "off", "max-len": "off", + "no-await-in-loop": "off", + "import/no-unresolved": [ + "error", + { + // Ignore Webpack query parameters, not supported by eslint-plugin-import + // https://github.com/import-js/eslint-plugin-import/issues/2562 + ignore: ["\\?react$"], + }, + ], }, settings: { "import/resolver": { diff --git a/@shared/api/internal.ts b/@shared/api/internal.ts index f3fcf3535..3901bff95 100644 --- a/@shared/api/internal.ts +++ b/@shared/api/internal.ts @@ -12,6 +12,7 @@ import { import { DataProvider } from "@stellar/wallet-sdk"; import BigNumber from "bignumber.js"; import { INDEXER_URL } from "@shared/constants/mercury"; +import { AssetsListItem, AssetsLists } from "@shared/constants/soroban/token"; import { getBalance, getDecimals, @@ -36,12 +37,18 @@ import { NETWORKS, } from "../constants/stellar"; import { SERVICE_TYPES } from "../constants/services"; +import { SorobanRpcNotSupportedError } from "../constants/errors"; import { APPLICATION_STATE } from "../constants/applicationState"; import { WalletType } from "../constants/hardwareWallet"; import { sendMessageToBackground } from "./helpers/extensionMessaging"; import { getIconUrlFromIssuer } from "./helpers/getIconUrlFromIssuer"; import { getDomainFromIssuer } from "./helpers/getDomainFromIssuer"; import { stellarSdkServer, submitTx } from "./helpers/stellarSdkServer"; +import { isCustomNetwork } from "@shared/helpers/stellar"; +import { + buildSorobanServer, + getNewTxBuilder, +} from "@shared/helpers/soroban/server"; const TRANSACTIONS_LIMIT = 100; @@ -518,13 +525,9 @@ export const getSorobanTokenBalance = async ( export const getAccountBalancesStandalone = async ({ publicKey, networkDetails, - sorobanClientServer, - sorobanClientTxBuilder, }: { publicKey: string; networkDetails: NetworkDetails; - sorobanClientServer: SorobanRpc.Server; - sorobanClientTxBuilder: () => Promise; }): Promise => { const { network, networkUrl, networkPassphrase } = networkDetails; @@ -583,6 +586,12 @@ export const getAccountBalancesStandalone = async ({ const tokensWithNoBalance = []; if (tokenIdList.length) { + if (!networkDetails.sorobanRpcUrl) { + throw new SorobanRpcNotSupportedError(); + } + + const server = buildSorobanServer(networkDetails.sorobanRpcUrl); + const params = [new Address(publicKey).toScVal()]; for (let i = 0; i < tokenIdList.length; i += 1) { @@ -596,13 +605,13 @@ export const getAccountBalancesStandalone = async ({ try { /* eslint-disable no-await-in-loop */ const { balance, symbol, ...rest } = await getSorobanTokenBalance( - sorobanClientServer, + server, tokenId, { - balance: await sorobanClientTxBuilder(), - name: await sorobanClientTxBuilder(), - decimals: await sorobanClientTxBuilder(), - symbol: await sorobanClientTxBuilder(), + balance: await getNewTxBuilder(publicKey, networkDetails, server), + name: await getNewTxBuilder(publicKey, networkDetails, server), + decimals: await getNewTxBuilder(publicKey, networkDetails, server), + symbol: await getNewTxBuilder(publicKey, networkDetails, server), }, params, ); @@ -687,6 +696,79 @@ export const getIndexerAccountHistory = async ({ } }; +export const getAccountHistory = async ( + publicKey: string, + networkDetails: NetworkDetails, +) => { + if (isCustomNetwork(networkDetails)) { + return await getAccountHistoryStandalone({ + publicKey, + networkDetails, + }); + } + return await getIndexerAccountHistory({ + publicKey, + networkDetails, + }); +}; + +export const getTokenDetails = async ({ + contractId, + publicKey, + networkDetails, +}: { + contractId: string; + publicKey: string; + networkDetails: NetworkDetails; +}): Promise<{ name: string; decimals: number; symbol: string } | null> => { + try { + if (isCustomNetwork(networkDetails)) { + if (!networkDetails.sorobanRpcUrl) { + throw new SorobanRpcNotSupportedError(); + } + + // You need one Tx Builder per call in Soroban right now + 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), + ); + const decimals = await getDecimals( + contractId, + server, + await getNewTxBuilder(publicKey, networkDetails, server), + ); + + return { + name, + symbol, + decimals, + }; + } + + const response = await fetch( + `${INDEXER_URL}/token-details/${contractId}?pub_key=${publicKey}&network=${networkDetails.network}`, + ); + const data = await response.json(); + if (!response.ok) { + throw new Error(data); + } + return data; + } catch (error) { + console.error(error); + captureException( + `Failed to fetch token details - ${JSON.stringify(error)}`, + ); + return null; + } +}; + export const getAssetIcons = async ({ balances, networkDetails, @@ -910,7 +992,7 @@ export const submitFreighterSorobanTransaction = async ({ } if (!networkDetails.sorobanRpcUrl) { - throw new Error("soroban rpc not supported"); + throw new SorobanRpcNotSupportedError(); } const serverUrl = networkDetails.sorobanRpcUrl || ""; @@ -1162,7 +1244,9 @@ export const editCustomNetwork = async ({ return response; }; -export const loadSettings = (): Promise => +export const loadSettings = (): Promise< + Settings & IndexerSettings & { assetsLists: AssetsLists } +> => sendMessageToBackground({ type: SERVICE_TYPES.LOAD_SETTINGS, }); @@ -1231,3 +1315,48 @@ export const removeTokenId = async ({ }); return resp.tokenIdList; }; + +export const addAssetsList = async ({ + assetsList, + network, +}: { + assetsList: AssetsListItem; + network: NETWORKS; +}) => { + let response = { + error: "", + assetsLists: {} as AssetsLists, + }; + + response = await sendMessageToBackground({ + type: SERVICE_TYPES.ADD_ASSETS_LIST, + assetsList, + network, + }); + + return { assetsLists: response.assetsLists, error: response.error }; +}; + +export const modifyAssetsList = async ({ + assetsList, + network, + isDeleteAssetsList, +}: { + assetsList: AssetsListItem; + network: NETWORKS; + isDeleteAssetsList: boolean; +}) => { + let response = { + error: "", + assetsLists: {} as AssetsLists, + }; + + response = await sendMessageToBackground({ + type: SERVICE_TYPES.MODIFY_ASSETS_LIST, + assetsList, + network, + isDeleteAssetsList, + }); + + return { assetsLists: response.assetsLists, error: response.error }; +}; diff --git a/@shared/api/tsconfig.json b/@shared/api/tsconfig.json index a8ed344e6..a92701faf 100644 --- a/@shared/api/tsconfig.json +++ b/@shared/api/tsconfig.json @@ -3,5 +3,5 @@ "compilerOptions": { "baseUrl": "." }, - "include": ["."] + "include": [".", "../helpers/stellar.ts"] } diff --git a/@shared/api/types.ts b/@shared/api/types.ts index 6bd0faf6e..99aa14976 100644 --- a/@shared/api/types.ts +++ b/@shared/api/types.ts @@ -1,12 +1,13 @@ import BigNumber from "bignumber.js"; import { Horizon } from "stellar-sdk"; import { Types } from "@stellar/wallet-sdk"; +import { AssetBalance, NativeBalance } from "@stellar/wallet-sdk/dist/types"; import { SERVICE_TYPES, EXTERNAL_SERVICE_TYPES } from "../constants/services"; import { APPLICATION_STATE } from "../constants/applicationState"; import { WalletType } from "../constants/hardwareWallet"; import { NetworkDetails } from "../constants/stellar"; -import { AssetBalance, NativeBalance } from "@stellar/wallet-sdk/dist/types"; +import { AssetsLists, AssetsListItem } from "../constants/soroban/token"; export enum ActionStatus { IDLE = "IDLE", @@ -53,11 +54,14 @@ export interface Response { isSorobanPublicEnabled: boolean; isRpcHealthy: boolean; userNotification: UserNotification; + assetsLists: AssetsLists; + assetsList: AssetsListItem; + isDeleteAssetsList: boolean; settingsState: SettingsState; networkDetails: NetworkDetails; sorobanRpcUrl: string; networksList: NetworkDetails[]; - allAccounts: Array; + allAccounts: Account[]; migratedAccounts: MigratedAccount[]; accountName: string; assetCode: string; @@ -66,7 +70,7 @@ export interface Response { network: string; networkIndex: number; networkName: string; - recentAddresses: Array; + recentAddresses: string[]; hardwareWalletType: WalletType; bipPath: string; blockedDomains: BlockedDomains; diff --git a/@shared/constants/errors.ts b/@shared/constants/errors.ts index e85f53f1f..b1a5f7000 100644 --- a/@shared/constants/errors.ts +++ b/@shared/constants/errors.ts @@ -1,3 +1,7 @@ export class NoExtensionInstalledError extends Error { message = "Freighter does not appear to be installed."; } + +export class SorobanRpcNotSupportedError extends Error { + message = "No Soroban RPC available"; +} diff --git a/@shared/constants/services.ts b/@shared/constants/services.ts index 8574f5f5f..86f78e2fd 100644 --- a/@shared/constants/services.ts +++ b/@shared/constants/services.ts @@ -45,6 +45,8 @@ export enum SERVICE_TYPES { GET_MIGRATABLE_ACCOUNTS = "GET_MIGRATABLE_ACCOUNTS", GET_MIGRATED_MNEMONIC_PHRASE = "GET_MIGRATED_MNEMONIC_PHRASE", MIGRATE_ACCOUNTS = "MIGRATE_ACCOUNTS", + ADD_ASSETS_LIST = "ADD_ASSETS_LIST", + MODIFY_ASSETS_LIST = "MODIFY_ASSETS_LIST", } export enum EXTERNAL_SERVICE_TYPES { diff --git a/@shared/constants/soroban/token.ts b/@shared/constants/soroban/token.ts index 4cfe97e65..290e0ec67 100644 --- a/@shared/constants/soroban/token.ts +++ b/@shared/constants/soroban/token.ts @@ -1,3 +1,5 @@ +import { NETWORKS } from "@shared/constants/stellar"; + // https://github.com/stellar/soroban-examples/blob/main/token/src/contract.rs export enum SorobanTokenInterface { transfer = "transfer", @@ -26,3 +28,38 @@ export interface SorobanToken { symbol: string; decimals: number; } + +export type AssetsListKey = NETWORKS.PUBLIC | NETWORKS.TESTNET; + +export type AssetsLists = { + [K in AssetsListKey]: AssetsListItem[]; +}; + +export interface AssetsListItem { + url: string; + isEnabled: boolean; +} + +export const DEFAULT_ASSETS_LISTS: AssetsLists = { + [NETWORKS.PUBLIC]: [ + { + url: "https://api.stellar.expert/explorer/public/asset-list/top50", + isEnabled: true, + }, + { + url: + "https://raw.githubusercontent.com/soroswap/token-list/main/tokenList.json", + isEnabled: true, + }, + { + url: "https://lobstr.co/api/v1/sep/assets/curated.json", + isEnabled: true, + }, + ], + [NETWORKS.TESTNET]: [ + { + url: "https://api.stellar.expert/explorer/testnet/asset-list/top50", + isEnabled: true, + }, + ], +}; diff --git a/@shared/helpers/soroban/server.ts b/@shared/helpers/soroban/server.ts index 2434dc7c5..762b2f031 100644 --- a/@shared/helpers/soroban/server.ts +++ b/@shared/helpers/soroban/server.ts @@ -5,7 +5,10 @@ import { Operation, SorobanRpc, scValToNative, + BASE_FEE, + TransactionBuilder, } from "stellar-sdk"; +import { NetworkDetails } from "@shared/constants/stellar"; export const simulateTx = async ( tx: Transaction, Operation[]>, @@ -19,3 +22,22 @@ export const simulateTx = async ( throw new Error("Invalid response from simulateTransaction"); }; + +export const buildSorobanServer = (serverUrl: string) => { + return new SorobanRpc.Server(serverUrl, { + allowHttp: serverUrl.startsWith("http://"), + }); +}; + +export const getNewTxBuilder = async ( + publicKey: string, + networkDetails: NetworkDetails, + server: SorobanRpc.Server, + fee = BASE_FEE, +) => { + const sourceAccount = await server.getAccount(publicKey); + return new TransactionBuilder(sourceAccount, { + fee, + networkPassphrase: networkDetails.networkPassphrase, + }); +}; diff --git a/@shared/helpers/stellar.ts b/@shared/helpers/stellar.ts new file mode 100644 index 000000000..458b83f8a --- /dev/null +++ b/@shared/helpers/stellar.ts @@ -0,0 +1,9 @@ +import { NetworkDetails } from "@shared/constants/stellar"; + +export const CUSTOM_NETWORK = "STANDALONE"; + +export const isCustomNetwork = (networkDetails: NetworkDetails) => { + const { network } = networkDetails; + + return network === CUSTOM_NETWORK; +}; diff --git a/README.md b/README.md index b0e6a548e..7c4878964 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Freighter -Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser. Learn more at [freighter.app](https://www.freighter.app/). +Freighter is a non-custodial wallet extension that enables you to sign Stellar transactions via your browser. Learn more at [freighter.app](https://www.freighter.app/). ## Yarn Workspaces diff --git a/extension/e2e-tests/addAsset.test.ts b/extension/e2e-tests/addAsset.test.ts new file mode 100644 index 000000000..8ab4560ff --- /dev/null +++ b/extension/e2e-tests/addAsset.test.ts @@ -0,0 +1,59 @@ +import { test, expect } from "./test-fixtures"; +import { loginToTestAccount, PASSWORD } from "./helpers/login"; + +test("Adding unverified Soroban token", async ({ page, extensionId }) => { + test.slow(); + await loginToTestAccount({ page, extensionId }); + + await page.getByText("Manage Assets").click({ force: true }); + await page.getByPlaceholder("Enter password").fill(PASSWORD); + await page.getByText("Log In").click({ force: true }); + + await expect(page.getByText("Choose Asset")).toBeVisible(); + await page.getByText("Add Soroban token").click({ force: true }); + await page + .getByTestId("search-token-input") + .fill("CAEOFUGMYLPQ7EGALPNB65N47EWSXLWMW6OWMRUQSQHBNSEIKQD2NCKV"); + await expect(page.getByTestId("asset-notification")).toHaveText( + "Not on your listsFreighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings.", + ); + await expect(page.getByTestId("ManageAssetCode")).toHaveText("E2E Token"); + await expect(page.getByTestId("ManageAssetRowButton")).toHaveText("Add"); + await page.getByTestId("ManageAssetRowButton").click({ force: true }); + + await expect(page.getByTestId("token-warning-notification")).toHaveText( + "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.", + ); + await page.getByTestId("add-asset").dispatchEvent("click"); + await expect(page.getByTestId("account-view")).toContainText("100 E2E"); +}); +test("Adding Soroban verified token", async ({ page, extensionId }) => { + // USDC: The verification status of this contract is subject to change. + // taken from: https://api.stellar.expert/explorer/testnet/asset-list/top50 + const verifiedToken = + "CAQCFVLOBK5GIULPNZRGATJJMIZL5BSP7X5YJVMGCPTUEPFM4AVSRCJU"; + + test.slow(); + await loginToTestAccount({ page, extensionId }); + + await page.getByText("Manage Assets").click({ force: true }); + await page.getByPlaceholder("Enter password").fill(PASSWORD); + await page.getByText("Log In").click({ force: true }); + + await expect(page.getByText("Choose Asset")).toBeVisible(); + await page.getByText("Add Soroban token").click({ force: true }); + await page.getByTestId("search-token-input").fill(verifiedToken); + await expect(page.getByTestId("asset-notification")).toHaveText( + "On your listsFreighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings.", + ); + await expect(page.getByTestId("ManageAssetCode")).toHaveText("USDC"); + await expect(page.getByTestId("ManageAssetRowButton")).toHaveText("Add"); + await page.getByTestId("ManageAssetRowButton").click({ force: true }); + + await expect(page.getByTestId("token-warning-notification")).toHaveText( + `This asset is part of the asset lists "StellarExpert Top 50."Freighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings. + `, + ); + await page.getByTestId("add-asset").dispatchEvent("click"); + await expect(page.getByTestId("account-view")).toBeVisible(); +}); diff --git a/extension/e2e-tests/helpers/login.ts b/extension/e2e-tests/helpers/login.ts index b018be37d..e13eadb1b 100644 --- a/extension/e2e-tests/helpers/login.ts +++ b/extension/e2e-tests/helpers/login.ts @@ -40,3 +40,45 @@ export const loginAndFund = async ({ page, extensionId }) => { timeout: 30000, }); }; + +export const loginToTestAccount = async ({ page, extensionId }) => { + await page.goto(`chrome-extension://${extensionId}/index.html`); + await page.getByText("Import Wallet").click(); + + const TEST_ACCOUNT_WORDS = [ + "card", + "whip", + "erosion", + "fatal", + "reunion", + "foil", + "doctor", + "embark", + "plug", + "note", + "thank", + "company", + ]; + + for (let i = 1; i <= TEST_ACCOUNT_WORDS.length; i++) { + await page.locator(`#MnemonicPhrase-${i}`).fill(TEST_ACCOUNT_WORDS[i - 1]); + } + + await page.locator("#password-input").fill(PASSWORD); + await page.locator("#confirm-password-input").fill(PASSWORD); + await page.locator("#termsOfUse-input").check({ force: true }); + await page.getByRole("button", { name: "Import" }).click(); + await expect(page.getByText("Wallet created successfully!")).toBeVisible({ + timeout: 20000, + }); + + await page.goto(`chrome-extension://${extensionId}/index.html#/account`); + await expect(page.getByTestId("network-selector-open")).toBeVisible({ + timeout: 10000, + }); + await page.getByTestId("network-selector-open").click(); + await page.getByText("Test Net").click(); + await expect(page.getByTestId("account-assets")).toBeVisible({ + timeout: 30000, + }); +}; diff --git a/extension/package.json b/extension/package.json index bd945bf11..8118c0494 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", @@ -35,7 +34,7 @@ "@types/node": "^12.0.0", "@types/qrcode.react": "^1.0.1", "@types/react-copy-to-clipboard": "^4.3.0", - "@types/react-dom": "^18.2.20", + "@types/react-dom": "^18.2.24", "@types/react-redux": "^7.1.7", "@types/react-router-dom": "^5.1.3", "@types/redux": "^3.6.0", @@ -60,6 +59,7 @@ "i18next-scanner-webpack": "^0.9.1", "jest-canvas-mock": "^2.4.0", "jest-environment-jsdom": "^28.1.3", + "jsonschema": "^1.4.1", "lodash": "^4.17.15", "mini-css-extract-plugin": "^1.6.2", "prettier": "^2.0.5", @@ -90,6 +90,7 @@ "devDependencies": { "@lavamoat/allow-scripts": "^2.3.1", "@playwright/test": "1.41.0", + "@svgr/webpack": "^8.1.0", "@types/semver": "^7.5.2", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", diff --git a/extension/src/background/helpers/account.ts b/extension/src/background/helpers/account.ts index 4acbe62d0..6fa954bc1 100644 --- a/extension/src/background/helpers/account.ts +++ b/extension/src/background/helpers/account.ts @@ -10,16 +10,14 @@ import { NETWORKS_LIST_ID, IS_EXPERIMENTAL_MODE_ID, HAS_ACCOUNT_SUBSCRIPTION, + ASSETS_LISTS_ID, } from "constants/localStorageTypes"; import { DEFAULT_NETWORKS, NetworkDetails } from "@shared/constants/stellar"; +import { DEFAULT_ASSETS_LISTS } from "@shared/constants/soroban/token"; import { getSorobanRpcUrl } from "@shared/helpers/soroban/sorobanRpcUrl"; +import { isCustomNetwork } from "@shared/helpers/stellar"; import { decodeString, encodeObject } from "helpers/urls"; -import { - isMainnet, - isTestnet, - isFuturenet, - isCustomNetwork, -} from "helpers/stellar"; +import { isMainnet, isTestnet, isFuturenet } from "helpers/stellar"; import { dataStorageAccess, browserLocalStorage, @@ -138,6 +136,16 @@ export const getNetworksList = async () => { return networksList; }; +export const getAssetsLists = async () => { + if (!(await localStore.getItem(ASSETS_LISTS_ID))) { + await localStore.setItem(ASSETS_LISTS_ID, DEFAULT_ASSETS_LISTS); + } + const assetLists = + (await localStore.getItem(ASSETS_LISTS_ID)) ?? DEFAULT_ASSETS_LISTS; + + return assetLists; +}; + export const getIsRpcHealthy = async (networkDetails: NetworkDetails) => { let rpcHealth = { status: "" }; if (isCustomNetwork(networkDetails)) { diff --git a/extension/src/background/helpers/dataStorage.ts b/extension/src/background/helpers/dataStorage.ts index 67c450ad4..dcaa3ecce 100644 --- a/extension/src/background/helpers/dataStorage.ts +++ b/extension/src/background/helpers/dataStorage.ts @@ -7,6 +7,7 @@ import { NETWORKS_LIST_ID, STORAGE_VERSION, TOKEN_ID_LIST, + ASSETS_LISTS_ID, } from "constants/localStorageTypes"; import { DEFAULT_NETWORKS, @@ -17,6 +18,7 @@ import { FUTURENET_NETWORK_DETAILS, SOROBAN_RPC_URLS, } from "@shared/constants/stellar"; +import { DEFAULT_ASSETS_LISTS } from "@shared/constants/soroban/token"; interface SetItemParams { [key: string]: any; @@ -149,8 +151,8 @@ const migrateTokenIdList = async () => { [NETWORKS.FUTURENET]: tokenIdsByKey, }; await localStore.setItem(TOKEN_ID_LIST, newTokenList); + await migrateDataStorageVersion("1.0.0"); } - await migrateDataStorageVersion("1.0.0"); }; const migrateTestnetSorobanRpcUrlNetworkDetails = async () => { @@ -253,6 +255,18 @@ export const resetAccountSubscriptions = async () => { if (!storageVersion || semver.eq(storageVersion, "4.0.2")) { // once account is unlocked, setup Mercury account subscription if !HAS_ACCOUNT_SUBSCRIPTION await localStore.setItem(HAS_ACCOUNT_SUBSCRIPTION, {}); + await migrateDataStorageVersion("4.0.2"); + } +}; + +export const addAssetsLists = async () => { + const localStore = dataStorageAccess(browserLocalStorage); + const storageVersion = (await localStore.getItem(STORAGE_VERSION)) as string; + + if (!storageVersion || semver.lt(storageVersion, "4.1.0")) { + // add the base asset lists + await localStore.setItem(ASSETS_LISTS_ID, DEFAULT_ASSETS_LISTS); + await migrateDataStorageVersion("4.1.0"); } }; @@ -265,6 +279,7 @@ export const versionedMigration = async () => { await migrateMainnetSorobanRpcUrlNetworkDetails(); await migrateSorobanRpcUrlNetwork(); await resetAccountSubscriptions(); + await addAssetsLists(); }; // Updates storage version diff --git a/extension/src/background/messageListener/popupMessageListener.ts b/extension/src/background/messageListener/popupMessageListener.ts index ed5bd3a67..1367eb4c7 100644 --- a/extension/src/background/messageListener/popupMessageListener.ts +++ b/extension/src/background/messageListener/popupMessageListener.ts @@ -36,6 +36,7 @@ import { MessageResponder } from "background/types"; import { ALLOWLIST_ID, APPLICATION_ID, + ASSETS_LISTS_ID, CACHED_ASSET_ICONS_ID, CACHED_ASSET_DOMAINS_ID, DATA_SHARING_ID, @@ -77,6 +78,7 @@ import { getSavedNetworks, getNetworkDetails, getNetworksList, + getAssetsLists, HW_PREFIX, getBipPath, subscribeTokenBalance, @@ -115,6 +117,10 @@ import { STELLAR_EXPERT_BLOCKED_DOMAINS_URL, STELLAR_EXPERT_BLOCKED_ACCOUNTS_URL, } from "background/constants/apiUrls"; +import { + AssetsListKey, + DEFAULT_ASSETS_LISTS, +} from "@shared/constants/soroban/token"; // number of public keys to auto-import const numOfPublicKeysToCheck = 5; @@ -1233,6 +1239,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { const featureFlags = await getFeatureFlags(); const isRpcHealthy = await getIsRpcHealthy(networkDetails); const userNotification = await getUserNotification(); + const assetsLists = await getAssetsLists(); return { allowList: await getAllowList(), @@ -1246,6 +1253,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { isSorobanPublicEnabled: featureFlags.useSorobanPublic, isRpcHealthy, userNotification, + assetsLists, }; }; @@ -1644,6 +1652,57 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { }; }; + const addAssetsList = async () => { + const { assetsList, network } = request; + + const currentAssetsLists = await getAssetsLists(); + + if ( + currentAssetsLists[network].some( + (list: { url: string }) => list.url === assetsList.url, + ) + ) { + return { + error: "Asset list already exists", + }; + } + + currentAssetsLists[network].push(assetsList); + + await localStore.setItem(ASSETS_LISTS_ID, currentAssetsLists); + + return { assetsLists: await getAssetsLists() }; + }; + + const modifyAssetsList = async () => { + const { assetsList, network, isDeleteAssetsList } = request; + + const currentAssetsLists = await getAssetsLists(); + const networkAssetsLists = currentAssetsLists[network]; + + const index = networkAssetsLists.findIndex( + ({ url }: { url: string }) => url === assetsList.url, + ); + + if ( + index < DEFAULT_ASSETS_LISTS[network as AssetsListKey].length && + isDeleteAssetsList + ) { + // if a user is somehow able to trigger a delete on a default asset list, return an error + return { error: "Unable to delete asset list" }; + } + + if (isDeleteAssetsList) { + networkAssetsLists.splice(index, 1); + } else { + networkAssetsLists.splice(index, 1, assetsList); + } + + await localStore.setItem(ASSETS_LISTS_ID, currentAssetsLists); + + return { assetsLists: await getAssetsLists() }; + }; + const messageResponder: MessageResponder = { [SERVICE_TYPES.CREATE_ACCOUNT]: createAccount, [SERVICE_TYPES.FUND_ACCOUNT]: fundAccount, @@ -1691,6 +1750,8 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { [SERVICE_TYPES.GET_MIGRATABLE_ACCOUNTS]: getMigratableAccounts, [SERVICE_TYPES.GET_MIGRATED_MNEMONIC_PHRASE]: getMigratedMnemonicPhrase, [SERVICE_TYPES.MIGRATE_ACCOUNTS]: migrateAccounts, + [SERVICE_TYPES.ADD_ASSETS_LIST]: addAssetsList, + [SERVICE_TYPES.MODIFY_ASSETS_LIST]: modifyAssetsList, }; return messageResponder[request.type](); diff --git a/extension/src/constants/localStorageTypes.ts b/extension/src/constants/localStorageTypes.ts index cac722814..e533bc8b5 100644 --- a/extension/src/constants/localStorageTypes.ts +++ b/extension/src/constants/localStorageTypes.ts @@ -20,3 +20,4 @@ export const METRICS_DATA = "metricsData"; export const TOKEN_ID_LIST = "tokenIdList"; export const STORAGE_VERSION = "storageVersion"; export const HAS_ACCOUNT_SUBSCRIPTION = "hasAccountSubscription"; +export const ASSETS_LISTS_ID = "assetsLists"; diff --git a/extension/src/helpers/__tests__/stellar.test.ts b/extension/src/helpers/__tests__/stellar.test.ts index eec08bde0..83e9371f5 100644 --- a/extension/src/helpers/__tests__/stellar.test.ts +++ b/extension/src/helpers/__tests__/stellar.test.ts @@ -2,6 +2,7 @@ import BigNumber from "bignumber.js"; import { getTransactionInfo, truncatedPublicKey, + truncateString, stroopToXlm, xlmToStroop, } from "../stellar"; @@ -20,6 +21,29 @@ describe("truncatedPublicKey", () => { }); }); +describe("truncateString", () => { + it("truncates using the correct default character count", () => { + const defaultCount = 4; + const str = truncateString( + "GAJVUHQV535IYW25XBTWTCUXNHLQN4F2PGIPOOX4DDKL2UPNXUHWU7B3", + ); + const [firstHalf, secondHalf] = str.split("…"); + expect(firstHalf.length).toBe(defaultCount); + expect(secondHalf.length).toBe(defaultCount); + }); + + it("truncates using the correct custom character count", () => { + const count = 5; + const str = truncateString( + "GAJVUHQV535IYW25XBTWTCUXNHLQN4F2PGIPOOX4DDKL2UPNXUHWU7B3", + count, + ); + const [firstHalf, secondHalf] = str.split("…"); + expect(firstHalf.length).toBe(count); + expect(secondHalf.length).toBe(count); + }); +}); + describe("getTransactionInfo", () => { it("detects https domain", () => { jest.spyOn(urls, "parsedSearchParam").mockReturnValue({ diff --git a/extension/src/helpers/metrics.ts b/extension/src/helpers/metrics.ts index 93b959358..7ecad5303 100644 --- a/extension/src/helpers/metrics.ts +++ b/extension/src/helpers/metrics.ts @@ -147,7 +147,7 @@ const getUserId = () => { * @param {object?} body An optional object containing event metadata * @returns {void} */ -export const emitMetric = (name: string, body?: any) => { +export const emitMetric = async (name: string, body?: any) => { const isDataSharingAllowed = settingsDataSharingSelector(store.getState()); if (!isDataSharingAllowed) { return; @@ -169,5 +169,5 @@ export const emitMetric = (name: string, body?: any) => { secret_key_account_funded: metricsData.importedFunded, /* eslint-enable */ }); - uploadMetrics(); + await uploadMetrics(); }; diff --git a/extension/src/helpers/stellar.ts b/extension/src/helpers/stellar.ts index b25870258..698ae2575 100644 --- a/extension/src/helpers/stellar.ts +++ b/extension/src/helpers/stellar.ts @@ -12,11 +12,11 @@ import { import { TransactionInfo } from "types/transactions"; import { parsedSearchParam, getUrlHostname } from "./urls"; -export const truncateString = (str: string) => - str ? `${str.slice(0, 4)}…${str.slice(-4)}` : ""; +export const truncateString = (str: string, charCount = 4) => + str ? `${str.slice(0, charCount)}…${str.slice(-charCount)}` : ""; -export const truncatedPublicKey = (publicKey: string) => - truncateString(publicKey); +export const truncatedPublicKey = (publicKey: string, charCount = 4) => + truncateString(publicKey, charCount); export const truncatedFedAddress = (addr: string) => { if (!addr || addr.indexOf("*") === -1) { @@ -157,11 +157,3 @@ export const isActiveNetwork = ( networkA: NetworkDetails, networkB: NetworkDetails, ) => isEqual(networkA, networkB); - -export const CUSTOM_NETWORK = "STANDALONE"; - -export const isCustomNetwork = (networkDetails: NetworkDetails) => { - const { network } = networkDetails; - - return network === CUSTOM_NETWORK; -}; diff --git a/extension/src/popup/Router.tsx b/extension/src/popup/Router.tsx index 007d4d32c..d2a871064 100644 --- a/extension/src/popup/Router.tsx +++ b/extension/src/popup/Router.tsx @@ -38,6 +38,7 @@ import { AccountHistory } from "popup/views/AccountHistory"; import { AccountCreator } from "popup/views/AccountCreator"; import { AddAccount } from "popup/views/AddAccount/AddAccount"; import { ManageConnectedApps } from "popup/views/ManageConnectedApps"; +import { ManageAssetsLists } from "popup/views/ManageAssetsLists"; import { ImportAccount } from "popup/views/AddAccount/ImportAccount"; import { SelectHardwareWallet } from "popup/views/AddAccount/connect/SelectHardwareWallet"; import { PluginWallet } from "popup/views/AddAccount/connect/PluginWallet"; @@ -74,7 +75,6 @@ import { SettingsState } from "@shared/api/types"; import { SignBlob } from "./views/SignBlob"; import { ReviewAuth } from "./views/ReviewAuth"; -import { SorobanProvider } from "./SorobanContext"; import { View } from "./basics/layout/View"; import { BottomNav } from "./components/BottomNav"; import { useIsSwap } from "./helpers/useIsSwap"; @@ -113,11 +113,7 @@ export const PublicKeyRoute = (props: RouteProps) => { /> ); } - return ( - - - - ); + return ; }; export const PrivateKeyRoute = (props: RouteProps) => { @@ -393,6 +389,9 @@ const Outlet = () => { + + + diff --git a/extension/src/popup/SorobanContext.tsx b/extension/src/popup/SorobanContext.tsx deleted file mode 100644 index 6140eec28..000000000 --- a/extension/src/popup/SorobanContext.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React from "react"; -import { useSelector } from "react-redux"; -import { BASE_FEE, SorobanRpc, TransactionBuilder } from "stellar-sdk"; -import { captureException } from "@sentry/browser"; - -import { SOROBAN_RPC_URLS, NETWORKS } from "@shared/constants/stellar"; - -import { settingsNetworkDetailsSelector } from "./ducks/settings"; - -export const hasSorobanClient = ( - context: SorobanContextInterface, -): context is Required => - context.server !== undefined && context.newTxBuilder !== undefined; - -export interface SorobanContextInterface { - server: SorobanRpc.Server; - newTxBuilder: (fee?: string) => Promise; -} - -export const SorobanContext = React.createContext( - {} as SorobanContextInterface, -); - -export const SorobanProvider = ({ - children, - pubKey, -}: { - children: React.ReactNode; - pubKey: string; -}) => { - const networkDetails = useSelector(settingsNetworkDetailsSelector); - - let server: SorobanContextInterface["server"]; - let newTxBuilder: SorobanContextInterface["newTxBuilder"]; - if (!networkDetails.sorobanRpcUrl) { - // handle any issues with a network missing sorobanRpcUrl - let serverUrl; - - switch (networkDetails.network) { - case NETWORKS.FUTURENET: - serverUrl = SOROBAN_RPC_URLS[NETWORKS.FUTURENET]; - break; - case NETWORKS.TESTNET: - serverUrl = SOROBAN_RPC_URLS[NETWORKS.TESTNET]; - break; - case NETWORKS.PUBLIC: - serverUrl = SOROBAN_RPC_URLS[NETWORKS.PUBLIC]; - break; - default: - serverUrl = SOROBAN_RPC_URLS[NETWORKS.TESTNET]; - } - - server = new SorobanRpc.Server(serverUrl, { - allowHttp: serverUrl.startsWith("http://"), - }); - - if (!server) { - captureException( - `Failed to instantiate SorobanContext on ${networkDetails.networkName} with ${networkDetails.sorobanRpcUrl}`, - ); - } - - newTxBuilder = async (fee = BASE_FEE) => { - const sourceAccount = await server!.getAccount(pubKey); - return new TransactionBuilder(sourceAccount, { - fee, - networkPassphrase: networkDetails.networkPassphrase, - }); - }; - } else { - server = new SorobanRpc.Server(networkDetails.sorobanRpcUrl, { - allowHttp: networkDetails.sorobanRpcUrl.startsWith("http://"), - }); - - newTxBuilder = async (fee = BASE_FEE) => { - const sourceAccount = await server!.getAccount(pubKey); - return new TransactionBuilder(sourceAccount, { - fee, - networkPassphrase: networkDetails.networkPassphrase, - }); - }; - } - - return ( - - {children} - - ); -}; diff --git a/extension/src/popup/__tests__/SorobanProvider.test.tsx b/extension/src/popup/__tests__/SorobanProvider.test.tsx deleted file mode 100644 index 21478d84f..000000000 --- a/extension/src/popup/__tests__/SorobanProvider.test.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import React, { useContext } from "react"; -import { render, waitFor, screen } from "@testing-library/react"; -import { APPLICATION_STATE as ApplicationState } from "@shared/constants/applicationState"; -import { DEFAULT_NETWORKS, NETWORKS } from "@shared/constants/stellar"; -import { Wrapper, mockAccounts } from "../__testHelpers__"; - -import { SorobanProvider, SorobanContext } from "../SorobanContext"; - -describe("SorobanProvider", () => { - const SorobanConsumer = () => { - const sorobanClient = useContext(SorobanContext); - - return ( -
- {sorobanClient.server.serverURL._parts.hostname} -
- ); - }; - - it("renders", async () => { - render( - - - - - , - ); - - await waitFor(() => screen.getByTestId("SorobanConsumer")); - expect(screen.getByTestId("SorobanConsumer")).toHaveTextContent( - "foo.stellar.org", - ); - }); - it("should find appropriate rpc url if one is missing", async () => { - render( - - - - - , - ); - await waitFor(() => screen.getByTestId("SorobanConsumer")); - expect(screen.getByTestId("SorobanConsumer")).toHaveTextContent( - "soroban-rpc-pubnet-prd.soroban-rpc-pubnet-prd.svc.cluster.local", - ); - }); -}); 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 000000000..59985c291 --- /dev/null +++ b/extension/src/popup/assets/icon-new-asset.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/extension/src/popup/assets/icon-security-asset-list.svg b/extension/src/popup/assets/icon-security-asset-list.svg new file mode 100644 index 000000000..99514a09b --- /dev/null +++ b/extension/src/popup/assets/icon-security-asset-list.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/extension/src/popup/assets/icon-security-connected.svg b/extension/src/popup/assets/icon-security-connected.svg new file mode 100644 index 000000000..62b5a0e78 --- /dev/null +++ b/extension/src/popup/assets/icon-security-connected.svg @@ -0,0 +1,3 @@ + + + diff --git a/extension/src/popup/assets/icon-security-phrase.svg b/extension/src/popup/assets/icon-security-phrase.svg new file mode 100644 index 000000000..aa8986d73 --- /dev/null +++ b/extension/src/popup/assets/icon-security-phrase.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/extension/src/popup/assets/icon-settings-about.svg b/extension/src/popup/assets/icon-settings-about.svg new file mode 100644 index 000000000..a4f99dcbe --- /dev/null +++ b/extension/src/popup/assets/icon-settings-about.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/extension/src/popup/assets/icon-settings-feedback.svg b/extension/src/popup/assets/icon-settings-feedback.svg new file mode 100644 index 000000000..2b6841129 --- /dev/null +++ b/extension/src/popup/assets/icon-settings-feedback.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/extension/src/popup/assets/icon-settings-help.svg b/extension/src/popup/assets/icon-settings-help.svg new file mode 100644 index 000000000..f3a48b999 --- /dev/null +++ b/extension/src/popup/assets/icon-settings-help.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/extension/src/popup/assets/icon-settings-logout.svg b/extension/src/popup/assets/icon-settings-logout.svg new file mode 100644 index 000000000..c5803e7b6 --- /dev/null +++ b/extension/src/popup/assets/icon-settings-logout.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/extension/src/popup/assets/icon-settings-network.svg b/extension/src/popup/assets/icon-settings-network.svg new file mode 100644 index 000000000..8b0bee251 --- /dev/null +++ b/extension/src/popup/assets/icon-settings-network.svg @@ -0,0 +1,3 @@ + + + diff --git a/extension/src/popup/assets/icon-settings-preferences.svg b/extension/src/popup/assets/icon-settings-preferences.svg new file mode 100644 index 000000000..8193f1198 --- /dev/null +++ b/extension/src/popup/assets/icon-settings-preferences.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/extension/src/popup/assets/icon-settings-security.svg b/extension/src/popup/assets/icon-settings-security.svg new file mode 100644 index 000000000..4a3a1a77a --- /dev/null +++ b/extension/src/popup/assets/icon-settings-security.svg @@ -0,0 +1,3 @@ + + + diff --git a/extension/src/popup/assets/icon-unverified.svg b/extension/src/popup/assets/icon-unverified.svg index a5bbfca83..883c5cc4c 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/basics/ListNavLink/index.tsx b/extension/src/popup/basics/ListNavLink/index.tsx index eb8060e5e..d4576c325 100644 --- a/extension/src/popup/basics/ListNavLink/index.tsx +++ b/extension/src/popup/basics/ListNavLink/index.tsx @@ -10,24 +10,30 @@ interface ListNavLinkProps { children: string | React.ReactNode; href: string | ROUTES; searchParams?: string; + icon?: React.ReactNode; } +const renderListNavLinkIcon = (icon: React.ReactNode) => ( +
{icon}
+); + export const ListNavLink = ({ children, href, searchParams = "", + icon, }: ListNavLinkProps) => { const fullHref = `${href}${searchParams}`; - return (
+ {icon ? renderListNavLinkIcon(icon) : null} {Object.values(ROUTES).includes(href as ROUTES) ? ( - {children} + {children} ) : ( - {children} + {children} )}
@@ -50,7 +56,7 @@ export const ListNavButtonLink = ({ handleClick(); }} > - {children} + {children} ); diff --git a/extension/src/popup/basics/ListNavLink/styles.scss b/extension/src/popup/basics/ListNavLink/styles.scss index 600e95177..10709b919 100644 --- a/extension/src/popup/basics/ListNavLink/styles.scss +++ b/extension/src/popup/basics/ListNavLink/styles.scss @@ -1,5 +1,9 @@ .ListNavLink { + align-items: center; line-height: 1.5rem; + display: flex; + gap: 0.75rem; + width: 100%; a, span { @@ -8,15 +12,29 @@ cursor: pointer; display: flex; justify-content: space-between; + width: 100%; } &__wrapper { display: flex; flex-direction: column; - gap: 0.75rem; + gap: 1rem; } &__icon { + display: flex; + align-items: center; + width: 1.25rem; + height: 1.25rem; + + svg { + width: 1.25rem; + height: 1.25rem; + } + } + + &__arrow { height: 1.25rem; + fill: var(--color-gray-50); } } diff --git a/extension/src/popup/basics/LoadingBackground/index.tsx b/extension/src/popup/basics/LoadingBackground/index.tsx index 6594b6912..de3cef7d7 100644 --- a/extension/src/popup/basics/LoadingBackground/index.tsx +++ b/extension/src/popup/basics/LoadingBackground/index.tsx @@ -5,16 +5,18 @@ import "./styles.scss"; interface LoadingBackgroundProps { onClick?: () => void; isActive: boolean; + isOpaque?: boolean; } export const LoadingBackground = ({ isActive, + isOpaque, onClick, }: LoadingBackgroundProps) => (
); diff --git a/extension/src/popup/basics/LoadingBackground/styles.scss b/extension/src/popup/basics/LoadingBackground/styles.scss index 04cef2782..954c64605 100644 --- a/extension/src/popup/basics/LoadingBackground/styles.scss +++ b/extension/src/popup/basics/LoadingBackground/styles.scss @@ -14,4 +14,9 @@ opacity: 1; z-index: 1; } + &--isOpaque { + background-color: rgba(var(--color-shadow-rgb), 1); + height: 100vh; + width: 100vw; + } } diff --git a/extension/src/popup/basics/buttons/BackButton/styles.scss b/extension/src/popup/basics/buttons/BackButton/styles.scss index d480045fd..ed7348abc 100644 --- a/extension/src/popup/basics/buttons/BackButton/styles.scss +++ b/extension/src/popup/basics/buttons/BackButton/styles.scss @@ -7,6 +7,10 @@ width: var(--back--button-dimension); z-index: var(--back--button-z-index); + svg { + fill: var(--color-gray-50); + } + &--has-copy { width: 3.75rem; } diff --git a/extension/src/popup/components/AssetNotification/index.tsx b/extension/src/popup/components/AssetNotification/index.tsx new file mode 100644 index 000000000..680d28634 --- /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 000000000..b7de195e4 --- /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/CopyValue/index.tsx b/extension/src/popup/components/CopyValue/index.tsx new file mode 100644 index 000000000..4814d62f8 --- /dev/null +++ b/extension/src/popup/components/CopyValue/index.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { CopyText, Icon } from "@stellar/design-system"; + +import "./styles.scss"; + +export const CopyValue = ({ + value, + displayValue, +}: { + value: string; + displayValue: string; +}) => ( + +
+ + {displayValue} +
+
+); diff --git a/extension/src/popup/components/CopyValue/styles.scss b/extension/src/popup/components/CopyValue/styles.scss new file mode 100644 index 000000000..82a43cdba --- /dev/null +++ b/extension/src/popup/components/CopyValue/styles.scss @@ -0,0 +1,8 @@ +.CopyValue { + display: flex; + align-items: center; + + .Value { + margin-left: 0.25rem; + } +} diff --git a/extension/src/popup/components/Onboarding/index.tsx b/extension/src/popup/components/Onboarding/index.tsx index 3eec33052..d306dc80c 100644 --- a/extension/src/popup/components/Onboarding/index.tsx +++ b/extension/src/popup/components/Onboarding/index.tsx @@ -97,7 +97,7 @@ export const OnboardingButtons = ({ {showBackButton ? ( + } diff --git a/extension/src/popup/components/SlideupModal/index.tsx b/extension/src/popup/components/SlideupModal/index.tsx index b6db18c4c..000603de9 100644 --- a/extension/src/popup/components/SlideupModal/index.tsx +++ b/extension/src/popup/components/SlideupModal/index.tsx @@ -19,15 +19,17 @@ export const SlideupModal = ({ const [slideupModalHeight, setSlideupModalHeight] = useState(-500); useEffect(() => { - setSlideupModalHeight(slideupModalRef.current?.clientHeight || 0); + const height = slideupModalRef.current?.clientHeight || 0; + setSlideupModalHeight(-height); }, [slideupModalRef]); + return ( <>
{children} diff --git a/extension/src/popup/components/WarningMessages/index.tsx b/extension/src/popup/components/WarningMessages/index.tsx index 8d96b6b03..13aaa07b2 100644 --- a/extension/src/popup/components/WarningMessages/index.tsx +++ b/extension/src/popup/components/WarningMessages/index.tsx @@ -1,13 +1,7 @@ -import React, { useContext, useState, useRef, useEffect } from "react"; +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,8 +13,10 @@ import { Networks, xdr, } from "stellar-sdk"; +import { captureException } from "@sentry/browser"; import { ActionStatus } from "@shared/api/types"; +import { getTokenDetails } from "@shared/api/internal"; import { xlmToStroop, isMainnet, isTestnet } from "helpers/stellar"; @@ -40,6 +36,7 @@ import { NewAssetFlags, } from "popup/components/manageAssets/ManageAssetRows"; import { SorobanTokenIcon } from "popup/components/account/AccountAssets"; +import { LoadingBackground } from "popup/basics/LoadingBackground"; import { View } from "popup/basics/layout/View"; import { useNetworkFees } from "popup/helpers/useNetworkFees"; import { @@ -56,10 +53,13 @@ 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 { INDEXER_URL } from "@shared/constants/mercury"; -import { searchToken } from "popup/helpers/searchAsset"; -import { captureException } from "@sentry/browser"; -import { SorobanContext } from "popup/SorobanContext"; +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"; @@ -305,7 +305,6 @@ export const ScamAssetWarning = ({ const { submitStatus } = useSelector(transactionSubmissionSelector); const [isSubmitting, setIsSubmitting] = useState(false); const isHardwareWallet = !!useSelector(hardwareWalletTypeSelector); - const sorobanClient = useContext(SorobanContext); const closeOverlay = () => { if (warningRef.current) { @@ -366,7 +365,6 @@ export const ScamAssetWarning = ({ publicKey, signedXDR: res.payload.signedTransaction, networkDetails, - sorobanClient, }), ); if (submitFreighterTransaction.fulfilled.match(submitResp)) { @@ -500,7 +498,6 @@ export const NewAssetWarning = ({ }) => { const { t } = useTranslation(); const dispatch: AppDispatch = useDispatch(); - const sorobanClient = useContext(SorobanContext); const warningRef = useRef(null); const { recommendedFee } = useNetworkFees(); const networkDetails = useSelector(settingsNetworkDetailsSelector); @@ -572,7 +569,6 @@ export const NewAssetWarning = ({ publicKey, signedXDR: res.payload.signedTransaction, networkDetails, - sorobanClient, }), ); if (submitFreighterTransaction.fulfilled.match(submitResp)) { @@ -693,16 +689,33 @@ export const NewAssetWarning = ({ ); }; -export const UnverifiedTokenWarning = ({ +export const UnverifiedTokenNotification = () => { + const { t } = useTranslation(); + + return ( + + ); +}; + +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(); @@ -749,98 +762,108 @@ export const UnverifiedTokenWarning = ({ setIsSubmitting(false); }; - return ( -
- -
-
-
- -
-
{code}
-
{domain}
-
-
- + return createPortal( + <> + +
+ +
+
+
+
-
- {t("Add Asset Trustline")} +
{code}
+
{domain}
+
+
+ +
+
+ {t("Add Asset Trustline")} +
-
- - -
-
- {t("Asset Info")} -
-
-
- -
-
-
+
+ {isVerifiedToken ? ( + {t( - "The asset is not part of Stellar Expert's top 50 assets list", + "Freighter uses asset lists to check assets you interact with. You can define your own assets lists in Settings.", )} + + ) : ( + + )} +
+ +
+
{t("Asset Info")}
+ + {isVerifiedToken ? null : ( +
+
+ unverified icon +
+
+
+ {t("Unverified asset")} +
+
+ {t("Proceed with caution")} +
+
+
+ )} +
+
+ new asset icon
-
- {t("This asset is not part of")}{" "} - - Stellar Expert's top 50 assets list - -
- - {t("Learn more")} - +
+
+ {t("New asset")} +
+
+ {t("This is a relatively new asset")} +
-
-
-
- - -
{" "} +
+
+ + +
{" "} +
-
- -
+ +
+ , + document.querySelector("#modal-root")!, ); }; @@ -908,7 +931,7 @@ export const UnverifiedTokenTransferWarning = ({ details: { contractId: string }[]; }) => { const { t } = useTranslation(); - const networkDetails = useSelector(settingsNetworkDetailsSelector); + const { networkDetails, assetsLists } = useSelector(settingsSelector); const [isUnverifiedToken, setIsUnverifiedToken] = useState(false); useEffect(() => { @@ -916,21 +939,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) { @@ -939,17 +957,17 @@ export const UnverifiedTokenTransferWarning = ({ }; fetchVerifiedTokens(); - }, [networkDetails, details]); + }, [networkDetails, details, assetsLists]); 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})`, )}

@@ -971,27 +989,24 @@ const WarningMessageTokenDetails = ({ const [tokenDetails, setTokenDetails] = React.useState( {} as Record, ); - - const tokenDetailsUrl = React.useCallback( - (contractId: string) => - `${INDEXER_URL}/token-details/${contractId}?pub_key=${publicKey}&network=${networkDetails.network}`, - [publicKey, networkDetails.network], - ); React.useEffect(() => { - async function getTokenDetails() { + async function _getTokenDetails() { setLoadingTokenDetails(true); const _tokenDetails = {} as Record< string, { name: string; symbol: string } >; try { - const response = await fetch(tokenDetailsUrl(transfer.contractId)); + const tokenDetailsResponse = await getTokenDetails({ + contractId: transfer.contractId, + publicKey, + networkDetails, + }); - if (!response.ok) { + if (!tokenDetailsResponse) { throw new Error("failed to fetch token details"); } - const details = await response.json(); - _tokenDetails[transfer.contractId] = details; + _tokenDetails[transfer.contractId] = tokenDetailsResponse; } catch (error) { // falls back to only showing contract ID captureException( @@ -1002,8 +1017,8 @@ const WarningMessageTokenDetails = ({ setTokenDetails(_tokenDetails); setLoadingTokenDetails(false); } - getTokenDetails(); - }, [transfer.contractId, tokenDetailsUrl]); + _getTokenDetails(); + }, [transfer.contractId, networkDetails, publicKey]); return (
@@ -1028,13 +1043,18 @@ const WarningMessageTokenDetails = ({

)}

- Contract ID: {transfer.contractId} + Contract ID: +

Amount: {transfer.amount}

- To: {transfer.to} + To: +

); diff --git a/extension/src/popup/components/WarningMessages/styles.scss b/extension/src/popup/components/WarningMessages/styles.scss index 09885dc09..fcc2295e2 100644 --- a/extension/src/popup/components/WarningMessages/styles.scss +++ b/extension/src/popup/components/WarningMessages/styles.scss @@ -265,7 +265,7 @@ } } -.UnverifiedTokenWarning { +.TokenWarning { z-index: var(--z-index--scam-warning); display: flex; position: fixed; @@ -282,7 +282,7 @@ display: flex; flex-direction: column; background: var(--color-gray-10); - padding: 2rem 1.5rem 1.5rem 1.5rem; + padding: 2rem 1.5rem 0.75rem 1.5rem; transition: margin var(--dropdown-animation); border-top-right-radius: 1rem; border-top-left-radius: 1rem; @@ -340,7 +340,7 @@ &__flag { display: flex; - margin-bottom: 1.5rem; + margin-bottom: 0.375rem; column-gap: 0.5rem; &__header { color: var(--color-white); @@ -352,8 +352,13 @@ font-size: 0.75rem; line-height: 1.25rem; } - &__icon { - color: var(--color-purple-50); + &__icon--unverified { + color: var(--color-yellow-50); + } + &__content { + color: var(--color-gray-70); + font-size: 0.875rem; + line-height: 1.125rem; } } @@ -385,6 +390,25 @@ line-height: 1.375rem; margin: 0; } + + .CopyText { + width: 100%; + .Floater { + .CopyText__content { + width: 100%; + .CopyValue { + width: 100%; + + .Value { + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } + } + } } } diff --git a/extension/src/popup/components/account/AccountAssets/index.tsx b/extension/src/popup/components/account/AccountAssets/index.tsx index a84b8079e..b149b0ca6 100644 --- a/extension/src/popup/components/account/AccountAssets/index.tsx +++ b/extension/src/popup/components/account/AccountAssets/index.tsx @@ -14,8 +14,8 @@ import StellarLogo from "popup/assets/stellar-logo.png"; import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; import { transactionSubmissionSelector } from "popup/ducks/transactionSubmission"; import { ScamAssetIcon } from "popup/components/account/ScamAssetIcon"; -import ImageMissingIcon from "popup/assets/image-missing.svg"; -import IconSoroban from "popup/assets/icon-soroban.svg"; +import ImageMissingIcon from "popup/assets/image-missing.svg?react"; +import IconSoroban from "popup/assets/icon-soroban.svg?react"; import "./styles.scss"; import { formatAmount } from "popup/helpers/formatters"; @@ -28,7 +28,7 @@ export const SorobanTokenIcon = ({ noMargin }: { noMargin?: boolean }) => ( noMargin ? "AccountAssets__asset--no-margin" : "" }`} > - icon soroban +
); @@ -118,7 +118,7 @@ export const AssetIcon = ({ ) : ( // the image path wasn't found, show a default broken image icon
- Asset icon missing +
); }; diff --git a/extension/src/popup/components/account/AccountAssets/styles.scss b/extension/src/popup/components/account/AccountAssets/styles.scss index 0b0619de4..a47eb4d45 100644 --- a/extension/src/popup/components/account/AccountAssets/styles.scss +++ b/extension/src/popup/components/account/AccountAssets/styles.scss @@ -44,6 +44,8 @@ $loader-light-color: #444961; } &--error { + width: 32px; + height: 32px; align-items: center; background: rgba(100, 50, 241, 0.32); border-radius: 2rem; @@ -57,6 +59,8 @@ $loader-light-color: #444961; } &--loading { + width: 32px; + height: 32px; background: linear-gradient( to right, $loader-dark-color 1%, diff --git a/extension/src/popup/components/accountHistory/AssetNetworkInfo/index.tsx b/extension/src/popup/components/accountHistory/AssetNetworkInfo/index.tsx index 58fc63e23..542e29375 100644 --- a/extension/src/popup/components/accountHistory/AssetNetworkInfo/index.tsx +++ b/extension/src/popup/components/accountHistory/AssetNetworkInfo/index.tsx @@ -6,6 +6,7 @@ import { getIconUrlFromIssuer } from "@shared/api/helpers/getIconUrlFromIssuer"; import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; import { transactionSubmissionSelector } from "popup/ducks/transactionSubmission"; import { ScamAssetIcon } from "popup/components/account/ScamAssetIcon"; +import { CopyValue } from "popup/components/CopyValue"; import StellarLogo from "popup/assets/stellar-logo.png"; import { displaySorobanId, isSorobanIssuer } from "popup/helpers/account"; @@ -60,6 +61,10 @@ export const AssetNetworkInfo = ({ if (networkIconUrl || assetType === "native") { return Network icon; } + if (!assetDomain) { + return null; + } + return
; }; @@ -68,7 +73,10 @@ export const AssetNetworkInfo = ({ <> {decideNetworkIcon()} {contractId ? ( - {displaySorobanId(contractId, 32)} + ) : ( {assetDomain || "Stellar Lumens"} )} diff --git a/extension/src/popup/components/accountHistory/AssetNetworkInfo/styles.scss b/extension/src/popup/components/accountHistory/AssetNetworkInfo/styles.scss index 20604c8be..870d92f72 100644 --- a/extension/src/popup/components/accountHistory/AssetNetworkInfo/styles.scss +++ b/extension/src/popup/components/accountHistory/AssetNetworkInfo/styles.scss @@ -8,13 +8,6 @@ line-height: 1.375rem; margin-top: 0.75rem; - &__icon { - background: var(--color-purple-60); - border-radius: 10rem; - height: var(--TransactionDetail--icon-dimension); - width: var(--TransactionDetail--icon-dimension); - } - img { height: var(--TransactionDetail--icon-dimension); width: var(--TransactionDetail--icon-dimension); diff --git a/extension/src/popup/components/accountHistory/HistoryItem/index.tsx b/extension/src/popup/components/accountHistory/HistoryItem/index.tsx index 10588c80c..646a85dd9 100644 --- a/extension/src/popup/components/accountHistory/HistoryItem/index.tsx +++ b/extension/src/popup/components/accountHistory/HistoryItem/index.tsx @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ // In order to allow that rule we need to refactor this to use the correct Horizon types and narrow operation types -import React, { useState, useEffect, useContext } from "react"; +import React, { useState, useEffect } from "react"; import { captureException } from "@sentry/browser"; import camelCase from "lodash/camelCase"; import { Icon, Loader } from "@stellar/design-system"; @@ -10,7 +10,6 @@ import { useTranslation } from "react-i18next"; import { OPERATION_TYPES } from "constants/transaction"; import { SorobanTokenInterface } from "@shared/constants/soroban/token"; -import { INDEXER_URL } from "@shared/constants/mercury"; import { METRIC_NAMES } from "popup/constants/metricsNames"; import { emitMetric } from "helpers/metrics"; @@ -19,7 +18,6 @@ import { getAttrsFromSorobanHorizonOp, } from "popup/helpers/soroban"; import { formatAmount } from "popup/helpers/formatters"; -import { isCustomNetwork } from "helpers/stellar"; import { AccountBalancesInterface, @@ -28,9 +26,8 @@ import { TokenBalance, } from "@shared/api/types"; import { NetworkDetails } from "@shared/constants/stellar"; -import { getDecimals, getName, getSymbol } from "@shared/helpers/soroban/token"; +import { getTokenDetails } from "@shared/api/internal"; -import { SorobanContext } from "popup/SorobanContext"; import { TransactionDetailProps } from "../TransactionDetail"; import "./styles.scss"; @@ -136,7 +133,6 @@ export const HistoryItem = ({ const [BodyComponent, setBodyComponent] = useState( null as React.ReactElement | null, ); - const sorobanClient = useContext(SorobanContext); const renderBodyComponent = () => BodyComponent; const renderIcon = () => IconComponent; @@ -247,34 +243,28 @@ export const HistoryItem = ({ setIsLoading(true); try { - if (isCustomNetwork(networkDetails)) { - const name = await getName( - attrs.contractId, - sorobanClient.server, - await sorobanClient.newTxBuilder(), - ); - const symbol = await getSymbol( - attrs.contractId, - sorobanClient.server, - await sorobanClient.newTxBuilder(), - ); - const decimals = await getDecimals( - attrs.contractId, - sorobanClient.server, - await sorobanClient.newTxBuilder(), - ); - const tokenDetails = { - name, - symbol, - decimals, - }; + const tokenDetailsResponse = await getTokenDetails({ + contractId: attrs.contractId, + publicKey, + networkDetails, + }); + + if (!tokenDetailsResponse) { + setRowText(operationString); + setTxDetails((_state) => ({ + ..._state, + headerTitle: t("Transaction"), + operationText: operationString, + })); + } else { const _token = { contractId: attrs.contractId, total: isRecieving ? attrs.amount : 0, - decimals: tokenDetails.decimals, - name: tokenDetails.name, - symbol: tokenDetails.symbol, + decimals: tokenDetailsResponse.decimals, + name: tokenDetailsResponse.name, + symbol: tokenDetailsResponse.symbol, }; + const formattedTokenAmount = formatTokenAmount( new BigNumber(attrs.amount), _token.decimals, @@ -301,76 +291,14 @@ export const HistoryItem = ({ to: attrs.to, }, headerTitle: `${t(capitalize(attrs.fnName))} ${ - tokenDetails.symbol + _token.symbol }`, isPayment: false, isRecipient: isRecieving, - operationText: `${formattedTokenAmount} ${tokenDetails.symbol}`, + operationText: `${formattedTokenAmount} ${_token.symbol}`, })); - setIsLoading(false); - } else { - const response = await fetch( - `${INDEXER_URL}/token-details/${attrs.contractId}?pub_key=${publicKey}&network=${networkDetails.network}`, - ); - - if (!response.ok) { - const _err = await response.json(); - captureException( - `Failed to fetch token details - ${JSON.stringify(_err)}`, - ); - - setRowText(operationString); - setTxDetails((_state) => ({ - ..._state, - headerTitle: t("Transaction"), - operationText: operationString, - })); - } else { - const tokenDetails = await response.json(); - - const _token = { - contractId: attrs.contractId, - total: isRecieving ? attrs.amount : 0, - decimals: tokenDetails.decimals, - name: tokenDetails.name, - symbol: tokenDetails.symbol, - }; - - const formattedTokenAmount = formatTokenAmount( - new BigNumber(attrs.amount), - _token.decimals, - ); - setBodyComponent( - <> - {isRecieving && "+"} - {formattedTokenAmount} {_token.symbol} - , - ); - - setDateText( - (_dateText) => - `${ - isRecieving ? t("Received") : t("Minted") - } \u2022 ${date}`, - ); - setRowText(t(capitalize(attrs.fnName))); - setTxDetails((_state) => ({ - ..._state, - operation: { - ..._state.operation, - from: attrs.from, - to: attrs.to, - }, - headerTitle: `${t(capitalize(attrs.fnName))} ${ - tokenDetails.symbol - }`, - isPayment: false, - isRecipient: isRecieving, - operationText: `${formattedTokenAmount} ${tokenDetails.symbol}`, - })); - } - setIsLoading(false); } + setIsLoading(false); } catch (error) { console.error(error); captureException(`Error fetching token details: ${error}`); @@ -509,7 +437,6 @@ export const HistoryItem = ({ t, to, accountBalances.balances, - sorobanClient, ]); return ( diff --git a/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx b/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx index 6ed8181a6..af1f6889b 100644 --- a/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx +++ b/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx @@ -11,13 +11,15 @@ import { View } from "popup/basics/layout/View"; import { emitMetric } from "helpers/metrics"; import { openTab } from "popup/helpers/navigate"; -import { stroopToXlm, isCustomNetwork } from "helpers/stellar"; +import { stroopToXlm } from "helpers/stellar"; import { useAssetDomain } from "popup/helpers/useAssetDomain"; import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; import { METRIC_NAMES } from "popup/constants/metricsNames"; import { HorizonOperation } from "@shared/api/types"; +import { isCustomNetwork } from "@shared/helpers/stellar"; + import "./styles.scss"; export interface TransactionDetailProps { diff --git a/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx b/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx index 5485edefb..3b1fd63b0 100644 --- a/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx +++ b/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect, useRef } from "react"; +import React, { useState, useEffect, useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import { Button, Icon } from "@stellar/design-system"; @@ -28,7 +28,6 @@ import { } from "popup/helpers/hardwareConnect"; import LedgerSigning from "popup/assets/ledger-signing.png"; import Ledger from "popup/assets/ledger.png"; -import { SorobanContext } from "popup/SorobanContext"; import "./styles.scss"; @@ -52,7 +51,6 @@ export const HardwareSign = ({ const [connectError, setConnectError] = useState(""); const isSwap = useIsSwap(); const [isDetectBtnDirty, setIsDetectBtnDirty] = useState(false); - const sorobanClient = useContext(SorobanContext); const closeOverlay = () => { if (hardwareConnectRef.current) { @@ -94,7 +92,6 @@ export const HardwareSign = ({ publicKey, signedXDR: res.payload, networkDetails, - sorobanClient, }), ); if ( diff --git a/extension/src/popup/components/identicons/KeyIdenticon/index.tsx b/extension/src/popup/components/identicons/KeyIdenticon/index.tsx index f62659899..adc0dbbb1 100644 --- a/extension/src/popup/components/identicons/KeyIdenticon/index.tsx +++ b/extension/src/popup/components/identicons/KeyIdenticon/index.tsx @@ -1,7 +1,8 @@ import React from "react"; +import { CopyText } from "@stellar/design-system"; -import { truncatedPublicKey } from "helpers/stellar"; - +import { CopyValue } from "popup/components/CopyValue"; +import { truncateString } from "helpers/stellar"; import { IdenticonImg } from "../IdenticonImg"; import "./styles.scss"; @@ -15,16 +16,21 @@ interface IdenticonWrapperElProps { } interface KeyIdenticonProps extends IdenticonWrapperElProps { + isCopyAllowed?: boolean; publicKey: string; + iconSide?: "left" | "right"; + keyTruncationAmount?: number; } export const KeyIdenticon = ({ + isCopyAllowed = false, publicKey = "", isSmall = false, customSize, + keyTruncationAmount, + iconSide = "left", ...props }: KeyIdenticonProps) => { - const shortPublicKey = truncatedPublicKey(publicKey); const customStyle = { ...(isSmall ? { @@ -32,24 +38,52 @@ export const KeyIdenticon = ({ "--Icon-padding": "0.2rem", // eslint-disable-next-line "--Icon-dimension": "1.5rem", + marginRight: iconSide === "left" ? "0.5rem" : 0, + marginLeft: iconSide === "right" ? "0.5rem" : 0, } - : {}), + : { + marginRight: !isCopyAllowed ? "0.5rem" : 0, + }), ...(customSize ? { // eslint-disable-next-line "--Icon-padding": customSize.padding, // eslint-disable-next-line "--Icon-dimension": customSize.dimension, + marginRight: iconSide === "left" ? "0.5rem" : 0, + marginLeft: iconSide === "right" ? "0.5rem" : 0, } - : {}), + : { + marginRight: !isCopyAllowed ? "0.5rem" : 0, + }), } as React.CSSProperties; return (
-
- -
- {shortPublicKey} + {iconSide === "left" && ( +
+ +
+ )} + {isCopyAllowed ? ( + + + + + + ) : ( + + {truncateString(publicKey, keyTruncationAmount)} + + )} + {iconSide === "right" && ( +
+ +
+ )}
); }; diff --git a/extension/src/popup/components/identicons/KeyIdenticon/styles.scss b/extension/src/popup/components/identicons/KeyIdenticon/styles.scss index 22a433d90..7c86d8ab7 100644 --- a/extension/src/popup/components/identicons/KeyIdenticon/styles.scss +++ b/extension/src/popup/components/identicons/KeyIdenticon/styles.scss @@ -10,9 +10,13 @@ border: 1px solid var(--color-gray-60); border-radius: 2rem; display: flex; - margin-right: 0.5rem; padding: var(--Icon-padding); height: var(--Icon-dimension); width: var(--Icon-dimension); } + + &--key { + width: 100%; + display: flex; + } } diff --git a/extension/src/popup/components/manageAssets/AddToken/index.tsx b/extension/src/popup/components/manageAssets/AddToken/index.tsx index 302d9b814..5de3a4aa1 100644 --- a/extension/src/popup/components/manageAssets/AddToken/index.tsx +++ b/extension/src/popup/components/manageAssets/AddToken/index.tsx @@ -1,38 +1,31 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -import React, { - useContext, - useEffect, - useCallback, - useRef, - useState, -} from "react"; +import React, { useEffect, useCallback, useRef, useState } from "react"; import { useSelector } from "react-redux"; import { captureException } from "@sentry/browser"; import { Formik, Form, Field, FieldProps } from "formik"; -import { Icon, Input, Link, Loader } from "@stellar/design-system"; +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 { NetworkDetails } from "@shared/constants/stellar"; +import { getTokenDetails } from "@shared/api/internal"; import { FormRows } from "popup/basics/Forms"; import { publicKeySelector } from "popup/ducks/accountServices"; -import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; -import { isCustomNetwork, isMainnet, isTestnet } from "helpers/stellar"; +import { + settingsNetworkDetailsSelector, + settingsSelector, +} from "popup/ducks/settings"; +import { isMainnet, isTestnet } from "helpers/stellar"; import { getVerifiedTokens, - TokenRecord, - searchTokenUrl, getNativeContractDetails, + VerifiedTokenRecord, } 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"; -import { SorobanContext } from "popup/SorobanContext"; import { ManageAssetRows, ManageAssetCurrency } from "../ManageAssetRows"; import "./styles.scss"; @@ -44,62 +37,6 @@ const initialValues: FormValues = { asset: "", }; -const VerificationBadge = ({ - isVerified, - networkDetails, -}: { - isVerified: boolean; - networkDetails: NetworkDetails; -}) => { - const { t } = useTranslation(); - const linkUrl = searchTokenUrl(networkDetails); - - return ( -
- {isVerified ? ( - <> - - - {t("This asset is part of")}{" "} - - Stellar Expert's top 50 assets list - - .{" "} - - {t("Learn more")} - - - - ) : ( - <> - unverified icon - - {t("This asset is not part of")}{" "} - - Stellar Expert's top 50 assets list - - .{" "} - - {t("Learn more")} - - - - )} -
- ); -}; - export const AddToken = () => { const { t } = useTranslation(); const publicKey = useSelector(publicKeySelector); @@ -111,8 +48,10 @@ export const AddToken = () => { const [isVerificationInfoShowing, setIsVerificationInfoShowing] = useState( false, ); + const [verifiedLists, setVerifiedLists] = useState([] as string[]); + const { assetsLists } = useSelector(settingsSelector); + const ResultsRef = useRef(null); - const sorobanClient = useContext(SorobanContext); const isAllowListVerificationEnabled = isMainnet(networkDetails) || isTestnet(networkDetails); @@ -131,12 +70,13 @@ 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 if (nativeContractDetails.contract === contractId) { // override our rules for verification for XLM + setIsVerificationInfoShowing(false); setAssetRows([ { code: nativeContractDetails.code, @@ -148,52 +88,30 @@ export const AddToken = () => { return; } - if (isCustomNetwork(networkDetails)) { - const name = await getName( - contractId, - sorobanClient.server, - await sorobanClient.newTxBuilder(), - ); - const symbol = await getSymbol( - contractId, - sorobanClient.server, - await sorobanClient.newTxBuilder(), - ); - - setAssetRows([ - { - code: symbol, - issuer: contractId, - domain: "", - name, - }, - ]); - setIsSearching(false); - return; - } - - const indexerLookup = async () => { + const tokenLookup = async () => { // lookup contract setIsVerifiedToken(false); - const tokenUrl = new URL(`${INDEXER_URL}/token-details/${contractId}`); - tokenUrl.searchParams.append("network", networkDetails.network); - tokenUrl.searchParams.append("pub_key", publicKey); - tokenUrl.searchParams.append( - "soroban_url", - networkDetails.sorobanRpcUrl!, - ); + let tokenDetailsResponse; - const res = await fetch(tokenUrl.href); - const resJson = await res.json(); - if (!res.ok) { - throw new Error(JSON.stringify(resJson)); + try { + tokenDetailsResponse = await getTokenDetails({ + contractId, + publicKey, + networkDetails, + }); + } catch (e) { + setAssetRows([]); + } + + if (!tokenDetailsResponse) { + setAssetRows([]); } else { setAssetRows([ { - code: resJson.symbol, + code: tokenDetailsResponse.symbol, issuer: contractId, domain: "", - name: resJson.name, + name: tokenDetailsResponse.name, }, ]); } @@ -204,14 +122,15 @@ export const AddToken = () => { verifiedTokens = await getVerifiedTokens({ networkDetails, contractId, - setIsSearching, + assetsLists, }); 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, @@ -220,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([]); @@ -231,7 +150,7 @@ export const AddToken = () => { } } else { // Futurenet token lookup - await indexerLookup(); + await tokenLookup(); } setIsVerificationInfoShowing(isAllowListVerificationEnabled); @@ -290,19 +209,16 @@ export const AddToken = () => {
) : 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 91161816c..ff4a8f551 100644 --- a/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx +++ b/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect } from "react"; +import React, { useState, useEffect } from "react"; import { StellarToml, Networks } from "stellar-sdk"; import { useDispatch, useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; @@ -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"; @@ -56,8 +56,6 @@ import { isContractId } from "popup/helpers/soroban"; import IconAdd from "popup/assets/icon-add.svg"; import IconRemove from "popup/assets/icon-remove.svg"; -import { SorobanContext } from "popup/SorobanContext"; - export type ManageAssetCurrency = StellarToml.Api.Currency & { domain: string; }; @@ -74,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 = ({ @@ -82,6 +90,8 @@ export const ManageAssetRows = ({ assetRows, chooseAsset, isVerifiedToken, + isVerificationInfoShowing, + verifiedLists, }: ManageAssetRowsProps) => { const { t } = useTranslation(); const publicKey = useSelector(publicKeySelector); @@ -114,8 +124,8 @@ export const ManageAssetRows = ({ code: "", issuer: "", image: "", - }); - const sorobanClient = useContext(SorobanContext); + isVerifiedToken: false, + } as SuspiciousAssetData); const server = stellarSdkServer(networkDetails.networkUrl); @@ -172,7 +182,6 @@ export const ManageAssetRows = ({ publicKey, signedXDR: res.payload.signedTransaction, networkDetails, - sorobanClient, }), ); @@ -182,7 +191,6 @@ export const ManageAssetRows = ({ getAccountBalances({ publicKey, networkDetails, - sorobanClient, }), ); trackChangeTrustline(); @@ -264,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, @@ -273,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( @@ -322,13 +331,15 @@ export const ManageAssetRows = ({ /> )} {showUnverifiedWarning && ( - { setShowUnverifiedWarning(false); }} + isVerifiedToken={!!suspiciousAssetData.isVerifiedToken} + verifiedLists={verifiedLists} /> )}
@@ -471,6 +482,9 @@ export const ManageAssetRow = ({ const { blockedDomains } = useSelector(transactionSubmissionSelector); const canonicalAsset = getCanonicalFromAsset(code, issuer); const isScamAsset = !!blockedDomains.domains[domain]; + const assetCode = name || code; + const truncatedAssetCode = + assetCode.length > 20 ? truncateString(assetCode) : assetCode; return ( <> @@ -481,7 +495,7 @@ export const ManageAssetRow = ({ />
- {name || code} + {truncatedAssetCode}
) => void; + selectedNetwork: AssetsListKey; + isLoading: boolean; +} + +const AssetListLink = ({ assetList }: { assetList: AssetsListsData }) => ( + +
+
{assetList.name}
+
{assetList.provider}
+
+
+); + +export const AssetLists = ({ + sortedAssetsListsData, + handleSelectChange, + selectedNetwork, + isLoading, +}: AssetListsProps) => { + const { t } = useTranslation(); + + return ( + <> + + + +
+ +
+ {isLoading ? ( +
+ +
+ ) : ( + <> +
+
+ {t("Enabled")} +
+ + {sortedAssetsListsData.enabled.map((assetList) => ( + + ))} + +
+ + {sortedAssetsListsData.disabled.length ? ( +
+
+ {t("Disabled")} +
+ + {sortedAssetsListsData.disabled.map((assetList) => ( + + ))} + +
+ ) : null} + + )} +
+ + + + + ); +}; diff --git a/extension/src/popup/components/manageAssetsLists/AssetLists/styles.scss b/extension/src/popup/components/manageAssetsLists/AssetLists/styles.scss new file mode 100644 index 000000000..d9417e672 --- /dev/null +++ b/extension/src/popup/components/manageAssetsLists/AssetLists/styles.scss @@ -0,0 +1,38 @@ +.ManageAssetsLists { + &__select { + padding-left: 1.625rem !important; + } + + &__network { + position: absolute; + top: 0.875rem; + left: 1.6875rem; + } + + &__loader { + align-items: center; + display: flex; + height: 100%; + justify-content: center; + } + + &__list { + margin-top: 1.5rem; + } + + &__badge { + display: flex; + margin-bottom: 1rem; + } + + &__title { + font-size: 0.875rem; + line-height: 1.25rem; + } + + &__subtitle { + font-size: 0.75rem; + color: var(--color-gray-70); + line-height: 1.125rem; + } +} diff --git a/extension/src/popup/components/manageAssetsLists/DeleteModal/index.tsx b/extension/src/popup/components/manageAssetsLists/DeleteModal/index.tsx new file mode 100644 index 000000000..99109b541 --- /dev/null +++ b/extension/src/popup/components/manageAssetsLists/DeleteModal/index.tsx @@ -0,0 +1,57 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { Button } from "@stellar/design-system"; + +import { LoadingBackground } from "popup/basics/LoadingBackground"; +import { View } from "popup/basics/layout/View"; + +import "./styles.scss"; + +interface DeleteModalProps { + handleCancel: () => void; + handleSubmit: () => void; +} + +export const DeleteModal = ({ + handleCancel, + handleSubmit, +}: DeleteModalProps) => { + const { t } = useTranslation(); + + return ( +
+ +
+
+ {t("Are you sure you want to delete this list?")} +
+
+ {t("Are you sure you want to delete this list?")}{" "} + {t("If you delete this list, you will have to re-add it manually.")} +
+
+ + +
+
+
+ +
+ ); +}; diff --git a/extension/src/popup/components/manageAssetsLists/DeleteModal/styles.scss b/extension/src/popup/components/manageAssetsLists/DeleteModal/styles.scss new file mode 100644 index 000000000..9eb879e2e --- /dev/null +++ b/extension/src/popup/components/manageAssetsLists/DeleteModal/styles.scss @@ -0,0 +1,38 @@ +.DeleteModal { + align-items: center; + z-index: var(--z-index--scam-warning); + display: flex; + position: fixed; + height: 100%; + width: 100%; + left: 0; + top: 0; + padding-top: 1.5rem; + + &__content { + background: var(--color-gray-10); + border-radius: 0.5rem; + border: 1px solid var(--color-gray-30); + padding: 1rem; + position: relative; + z-index: 2; + } + + &__title { + line-height: 1.5rem; + } + + &__body { + color: var(--color-gray-70); + font-size: 0.875rem; + line-height: 1.5rem; + margin-top: 0.5rem; + } + + &__button-row { + margin-top: 1rem; + display: flex; + direction: row; + gap: 0.5rem; + } +} diff --git a/extension/src/popup/components/manageAssetsLists/ModifyAssetList/index.tsx b/extension/src/popup/components/manageAssetsLists/ModifyAssetList/index.tsx new file mode 100644 index 000000000..dcdb5687c --- /dev/null +++ b/extension/src/popup/components/manageAssetsLists/ModifyAssetList/index.tsx @@ -0,0 +1,377 @@ +import React, { useEffect, useState } from "react"; +import { Button, Input, Toggle, Notification } from "@stellar/design-system"; +import { Formik, Form, Field, FieldProps } from "formik"; +import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; +import { useLocation } from "react-router-dom"; +import { captureException } from "@sentry/browser"; + +import { + DEFAULT_ASSETS_LISTS, + AssetsListKey, +} from "@shared/constants/soroban/token"; +import { ROUTES } from "popup/constants/routes"; +import { AppDispatch } from "popup/App"; + +import { AssetsListsData } from "popup/views/ManageAssetsLists"; +import { SubviewHeader } from "popup/components/SubviewHeader"; +import { View } from "popup/basics/layout/View"; +import { addAssetsList, modifyAssetsList } from "popup/ducks/settings"; +import { navigateTo } from "popup/helpers/navigate"; +import { schemaValidatedAssetList } from "popup/helpers/searchAsset"; + +import "./styles.scss"; +import { DeleteModal } from "../DeleteModal"; + +interface ModifyAssetListProps { + selectedNetwork: AssetsListKey; + assetsListsData: AssetsListsData[]; +} + +interface FormValues { + assetList: string; +} + +export const ModifyAssetList = ({ + selectedNetwork, + assetsListsData, +}: ModifyAssetListProps) => { + const { t } = useTranslation(); + const location = useLocation(); + const params = new URLSearchParams(location.search); + const assetListUrl = params.get("asset-list-url"); + const dispatch: AppDispatch = useDispatch(); + const [fetchErrorString, setFetchErrorString] = useState(""); + const [submitErrorString, setSubmitErrorString] = useState(""); + const [assetListInfo, setAssetListInfo] = useState({} as AssetsListsData); + const [isFetchingAssetList, setIsFetchingAssetList] = useState(false); + const [isEditing, setIsEditing] = useState(false); + const [isDefaultAssetList, setIsDefaultAssetList] = useState(false); + const [isShowingDeleteModal, setIsShowingDeleteModal] = useState(false); + + const defaultAssetsList = DEFAULT_ASSETS_LISTS[selectedNetwork]; + + useEffect(() => { + if (assetListUrl) { + /* Based on the query param, we're in EDIT mode. Prepopulate some information */ + const decodedAssetListUrl = decodeURIComponent(assetListUrl); + const assetsListsSelection = assetsListsData.find( + ({ url }) => url === decodedAssetListUrl, + ); + if (assetsListsSelection) { + const { + url, + name, + description, + provider, + isEnabled, + } = assetsListsSelection; + setAssetListInfo({ + url, + name, + description, + provider, + isEnabled, + }); + setIsEditing(true); + + if ( + defaultAssetsList.find( + ({ url: defaultUrl }) => defaultUrl === decodedAssetListUrl, + ) + ) { + // this is a default network, disable some features + setIsDefaultAssetList(true); + } + } + } + }, [assetsListsData, assetListUrl, defaultAssetsList]); + + const handleSearch = async (event: React.MouseEvent, values: FormValues) => { + let url; + let res; + setIsFetchingAssetList(true); + setFetchErrorString(""); + setSubmitErrorString(""); + setAssetListInfo({} as AssetsListsData); + event.preventDefault(); + + try { + url = new URL(values.assetList); + } catch (err) { + console.error(err); + setFetchErrorString("Unable to parse URL"); + setIsFetchingAssetList(false); + return; + } + + try { + res = await fetch(url); + } catch (err) { + console.error(err); + setFetchErrorString("Unable to fetch asset list"); + setIsFetchingAssetList(false); + return; + } + + if (!res.ok) { + setFetchErrorString("Unable to fetch asset list"); + setIsFetchingAssetList(false); + return; + } + + const resJson = await res.json(); + + // check against the SEP-0042 schema + const validatedList = await schemaValidatedAssetList(resJson); + + if (!validatedList) { + captureException("Unable to fetch SEP-0042 JSON schema"); + setFetchErrorString("Unable to validate asset asset list"); + return; + } + + if (validatedList.errors?.length) { + const errors = validatedList.errors.map( + ({ stack }: { stack: string }) => stack, + ); + + setFetchErrorString( + `Fetched asset list does not conform to schema: ${JSON.stringify( + errors.join(" | "), + )}`, + ); + setIsFetchingAssetList(false); + return; + } + + if (resJson.network !== selectedNetwork.toLowerCase()) { + const getNetworkName = (network: string) => + network === "public" ? "Mainnet" : "Testnet"; + setFetchErrorString( + `The entered asset list belongs to "${getNetworkName( + resJson.network as string, + )}": Currently editing "${getNetworkName( + selectedNetwork.toLowerCase(), + )}" lists.`, + ); + setIsFetchingAssetList(false); + return; + } + + setFetchErrorString(""); + setAssetListInfo({ + url: values.assetList, + name: resJson.name, + description: resJson.description, + provider: resJson.provider, + isEnabled: + assetListInfo.isEnabled === undefined ? true : assetListInfo.isEnabled, + }); + + setIsFetchingAssetList(false); + }; + + /* handle editing an exisiting asset list's "enabled" status */ + const handleIsEnabledChange = async ( + e: React.ChangeEvent, + ) => { + setAssetListInfo({ + ...assetListInfo, + isEnabled: e.target.checked, + }); + if (isEditing) { + await dispatch( + modifyAssetsList({ + assetsList: { + url: assetListInfo.url, + isEnabled: e.target.checked, + }, + network: selectedNetwork, + isDeleteAssetsList: false, + }), + ); + } + }; + + /* handle adding a brand a new asset list */ + const handleAddAssetList = async (values: FormValues) => { + const assetsList = { + url: values.assetList, + isEnabled: assetListInfo.isEnabled, + }; + const addAssetsListResp = await dispatch( + addAssetsList({ assetsList, network: selectedNetwork }), + ); + + if (addAssetsList.rejected.match(addAssetsListResp)) { + setSubmitErrorString( + addAssetsListResp.payload?.errorMessage || "Unable to save asset list", + ); + } + + if (addAssetsList.fulfilled.match(addAssetsListResp)) { + navigateTo(ROUTES.manageAssetsLists); + } + }; + + /* handle deleting an existing asset list */ + const handleEditAssetList = async () => { + const modifyAssetsListResp = await dispatch( + modifyAssetsList({ + assetsList: { + url: assetListInfo.url, + isEnabled: assetListInfo.isEnabled, + }, + network: selectedNetwork, + isDeleteAssetsList: true, + }), + ); + + if (modifyAssetsList.rejected.match(modifyAssetsListResp)) { + setSubmitErrorString( + modifyAssetsListResp.payload?.errorMessage || + "Unable to delete asset list", + ); + } + + if (modifyAssetsList.fulfilled.match(modifyAssetsListResp)) { + navigateTo(ROUTES.manageAssetsLists); + } + }; + + /* Show the confirm delete modal */ + const handleShowDeleteModal = () => { + setIsShowingDeleteModal(true); + }; + + return ( + <> + + + + {({ isSubmitting, isValid, errors, values, setSubmitting }) => ( +
+ {isShowingDeleteModal ? ( + { + setIsShowingDeleteModal(false); + setSubmitting(false); + }} + handleSubmit={handleEditAssetList} + /> + ) : null} +
+ +
+ + {({ field }: FieldProps) => ( + + )} + +
+
+ +
+ {Object.keys(assetListInfo).length ? ( + <> +
+
+ {assetListInfo.name} +
+
+ {t("by")} {assetListInfo.provider} +
+
+ {assetListInfo.description} +
+
+
+ + ) => + handleIsEnabledChange(e) + } + /> +
+ + ) : null} + {fetchErrorString ? ( + +
+ {fetchErrorString} +
+
+ ) : null} + {submitErrorString ? ( +
+ +
+ ) : null} +
+ + {isEditing ? ( + + ) : ( + + )} + + )} +
+
+ + ); +}; diff --git a/extension/src/popup/components/manageAssetsLists/ModifyAssetList/styles.scss b/extension/src/popup/components/manageAssetsLists/ModifyAssetList/styles.scss new file mode 100644 index 000000000..0cb38e5bb --- /dev/null +++ b/extension/src/popup/components/manageAssetsLists/ModifyAssetList/styles.scss @@ -0,0 +1,67 @@ +.ModifyAssetList { + &__label { + color: var(--color-gray-70); + font-size: 0.75rem; + line-height: 1.125rem; + margin-bottom: 0.375rem; + } + + &__input { + margin-bottom: 0.5rem; + } + + &__results { + display: flex; + flex-direction: column; + height: 20rem; + margin: 0.75rem 0; + overflow: scroll; + + .View__inset { + height: 100%; + } + } + + &__enable { + margin-top: 2rem; + display: flex; + gap: 0.5rem; + justify-content: space-between; + } + + &__submit-error { + margin-top: auto; + } + + &__not-found { + color: var(--color-red-70); + font-size: 0.75rem; + line-height: 1.125rem; + margin: 0 0 1.25rem 0; + } + + &__info { + background: var(--color-gray-20); + border-radius: 0.5rem; + padding: 0.75rem 1rem; + + &__title { + color: var(--color-gray-80); + font-size: 0.875rem; + line-height: 1.25rem; + } + + &__provider { + color: var(--color-gray-70); + font-size: 0.75rem; + line-height: 1.125rem; + } + + &__description { + color: var(--color-gray-70); + font-size: 0.75rem; + line-height: 1.125rem; + margin-top: 0.75rem; + } + } +} diff --git a/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx b/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx index 4f84b3f9b..b52abc5dd 100644 --- a/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx +++ b/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx @@ -7,6 +7,7 @@ import { object as YupObject, string as YupString } from "yup"; import { useHistory, useLocation } from "react-router-dom"; import { NETWORKS } from "@shared/constants/stellar"; +import { CUSTOM_NETWORK } from "@shared/helpers/stellar"; import { AppDispatch } from "popup/App"; import { PillButton } from "popup/basics/buttons/PillButton"; @@ -15,7 +16,7 @@ import { ROUTES } from "popup/constants/routes"; import { navigateTo } from "popup/helpers/navigate"; import { isNetworkUrlValid as isNetworkUrlValidHelper } from "popup/helpers/account"; -import { CUSTOM_NETWORK, isActiveNetwork } from "helpers/stellar"; +import { isActiveNetwork } from "helpers/stellar"; import { addCustomNetwork, diff --git a/extension/src/popup/components/manageNetwork/NetworkForm/styles.scss b/extension/src/popup/components/manageNetwork/NetworkForm/styles.scss index b9eb5e545..ddf66bf74 100644 --- a/extension/src/popup/components/manageNetwork/NetworkForm/styles.scss +++ b/extension/src/popup/components/manageNetwork/NetworkForm/styles.scss @@ -7,8 +7,6 @@ } &__body { - margin-left: -1.5rem; - &:nth-of-type(3) { margin-top: 0.5rem; } diff --git a/extension/src/popup/components/mnemonicPhrase/CheckButton/index.tsx b/extension/src/popup/components/mnemonicPhrase/CheckButton/index.tsx index 774afa352..bb56c2ed1 100644 --- a/extension/src/popup/components/mnemonicPhrase/CheckButton/index.tsx +++ b/extension/src/popup/components/mnemonicPhrase/CheckButton/index.tsx @@ -5,12 +5,18 @@ import "./styles.scss"; type CheckButtonProps = { onChange: (e: any) => void; + onKeyDown?: (e: any) => void; wordKey: string; word: string; }; -export const CheckButton = ({ onChange, wordKey, word }: CheckButtonProps) => ( - <> +export const CheckButton = ({ + onChange, + onKeyDown, + wordKey, + word, +}: CheckButtonProps) => ( + ( name={wordKey} key={wordKey} text={word} + onKeyDown={(e: React.KeyboardEvent) => { + if (onKeyDown) { + onKeyDown(e); + } + }} /> - + ); diff --git a/extension/src/popup/components/mnemonicPhrase/CheckButton/styles.scss b/extension/src/popup/components/mnemonicPhrase/CheckButton/styles.scss index 98022977c..9c917fa50 100644 --- a/extension/src/popup/components/mnemonicPhrase/CheckButton/styles.scss +++ b/extension/src/popup/components/mnemonicPhrase/CheckButton/styles.scss @@ -1,3 +1,5 @@ +@use "../../../styles/utils.scss" as *; + .ButtonLabel { border: 1px solid var(--color-gray-10); border-radius: 6.25rem; @@ -12,7 +14,11 @@ } .CheckButton { - display: none; + opacity: 0; + + &:focus + label { + @include native-like-outline; + } &:checked + label { background: var(--color-gray-10); diff --git a/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/index.tsx b/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/index.tsx index f090e4dec..56177dd7d 100644 --- a/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/index.tsx +++ b/extension/src/popup/components/mnemonicPhrase/ConfirmMnemonicPhrase/index.tsx @@ -125,6 +125,14 @@ export const ConfirmMnemonicPhrase = ({ handleChange(e); updatePhrase(e.target as HTMLInputElement); }} + onKeyDown={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + e.target.checked = e.target.value !== "true"; + handleChange(e); + updatePhrase(e.target as HTMLInputElement); + } + }} wordKey={wordKey} word={convertToWord(wordKey)} /> diff --git a/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx b/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx index 8b1f6703c..34cfc0b9b 100644 --- a/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx +++ b/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx @@ -1,12 +1,13 @@ import React, { useEffect, useState } from "react"; import { useSelector, useDispatch } from "react-redux"; -import { Icon, Link, Notification } from "@stellar/design-system"; -import { useTranslation } from "react-i18next"; +import { Icon } from "@stellar/design-system"; import { ROUTES } from "popup/constants/routes"; import { navigateTo } from "popup/helpers/navigate"; import { isMainnet, isTestnet } from "helpers/stellar"; import { AssetIcon } from "popup/components/account/AccountAssets"; +import { ScamAssetIcon } from "popup/components/account/ScamAssetIcon"; +import { UnverifiedTokenNotification } from "popup/components/WarningMessages"; import { transactionSubmissionSelector, saveAssetSelectSource, @@ -16,8 +17,7 @@ import { 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"; @@ -29,10 +29,9 @@ export const AssetSelect = ({ assetCode: string; issuerKey: string; }) => { - 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 +48,7 @@ export const AssetSelect = ({ const verifiedTokens = await getVerifiedTokens({ networkDetails, contractId: issuerKey, + assetsLists, }); if (!verifiedTokens.length) { @@ -57,7 +57,7 @@ export const AssetSelect = ({ }; fetchVerifiedTokens(); - }, [issuerKey, networkDetails]); + }, [issuerKey, networkDetails, assetsLists]); const handleSelectAsset = () => { dispatch(saveAssetSelectType(AssetSelectType.REGULAR)); @@ -69,24 +69,7 @@ export const AssetSelect = ({ <> {isUnverifiedToken ? (
- - {t("This asset is not part of")}{" "} - - Stellar Expert's top 50 assets list - - .{" "} - - {t("Learn more")} - - +
) : null}
void }) => { const { t } = useTranslation(); const isSwap = useIsSwap(); const dispatch: AppDispatch = useDispatch(); - const sorobanClient = useContext(SorobanContext); const sourceAsset = getAssetFromCanonical(asset); const { recommendedFee } = useNetworkFees(); @@ -144,7 +142,6 @@ export const SubmitSuccess = ({ viewDetails }: { viewDetails: () => void }) => { publicKey, signedXDR: res.payload.signedTransaction, networkDetails, - sorobanClient, }), ); diff --git a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx index fe89dde40..bce466b34 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx +++ b/extension/src/popup/components/sendPayment/SendConfirm/TransactionDetails/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect } from "react"; +import React, { useState, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import BigNumber from "bignumber.js"; import { @@ -19,12 +19,12 @@ import { xlmToStroop, getConversionRate, truncatedFedAddress, - isCustomNetwork, } from "helpers/stellar"; import { getStellarExpertUrl } from "popup/helpers/account"; import { stellarSdkServer } from "@shared/api/helpers/stellarSdkServer"; import { AssetIcons, ActionStatus } from "@shared/api/types"; import { getIconUrlFromIssuer } from "@shared/api/helpers/getIconUrlFromIssuer"; +import { isCustomNetwork } from "@shared/helpers/stellar"; import { AppDispatch } from "popup/App"; import { ROUTES } from "popup/constants/routes"; @@ -66,7 +66,6 @@ import { View } from "popup/basics/layout/View"; import { TRANSACTION_WARNING } from "constants/transaction"; import { formatAmount } from "popup/helpers/formatters"; -import { SorobanContext } from "popup/SorobanContext"; import "./styles.scss"; @@ -191,7 +190,6 @@ const getOperation = ( export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { const dispatch: AppDispatch = useDispatch(); - const sorobanClient = useContext(SorobanContext); const submission = useSelector(transactionSubmissionSelector); const { destinationBalances, @@ -288,7 +286,6 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { publicKey, signedXDR: res.payload.signedTransaction, networkDetails, - sorobanClient, }), ); @@ -358,7 +355,6 @@ export const TransactionDetails = ({ goBack }: { goBack: () => void }) => { publicKey, signedXDR: res.payload.signedTransaction, networkDetails, - sorobanClient, }), ); diff --git a/extension/src/popup/components/sendPayment/SendSettings/Settings/index.tsx b/extension/src/popup/components/sendPayment/SendSettings/Settings/index.tsx index 68ff8bf81..8661a62e9 100644 --- a/extension/src/popup/components/sendPayment/SendSettings/Settings/index.tsx +++ b/extension/src/popup/components/sendPayment/SendSettings/Settings/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect } from "react"; +import React, { useEffect } from "react"; import { useSelector, useDispatch } from "react-redux"; import { Formik, Form, Field, FieldProps } from "formik"; import { Icon, Textarea, Link, Button } from "@stellar/design-system"; @@ -29,7 +29,6 @@ import { parseTokenAmount } from "popup/helpers/soroban"; import "../../styles.scss"; import { Balances, TokenBalance } from "@shared/api/types"; import { AppDispatch } from "popup/App"; -import { SorobanContext } from "popup/SorobanContext"; export const Settings = ({ previous, @@ -40,7 +39,6 @@ export const Settings = ({ }) => { const { t } = useTranslation(); const dispatch: AppDispatch = useDispatch(); - const sorobanClient = useContext(SorobanContext); const { asset, amount, @@ -105,7 +103,6 @@ export const Settings = ({ memo, params, networkDetails, - sorobanClient, transactionFee, }), ); diff --git a/extension/src/popup/components/sendPayment/SendTo/index.tsx b/extension/src/popup/components/sendPayment/SendTo/index.tsx index ae1c43686..5732dce5e 100644 --- a/extension/src/popup/components/sendPayment/SendTo/index.tsx +++ b/extension/src/popup/components/sendPayment/SendTo/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect, useCallback } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { useSelector, useDispatch } from "react-redux"; import debounce from "lodash/debounce"; import { Asset, StrKey, MuxedAccount, Federation } from "stellar-sdk"; @@ -39,7 +39,6 @@ import { transactionSubmissionSelector, getDestinationBalances, } from "popup/ducks/transactionSubmission"; -import { SorobanContext } from "popup/SorobanContext"; import "../styles.scss"; @@ -98,7 +97,6 @@ const InvalidAddressWarning = () => { export const SendTo = ({ previous }: { previous: ROUTES }) => { const { t } = useTranslation(); const dispatch: AppDispatch = useDispatch(); - const sorobanClient = useContext(SorobanContext); const { destination, federationAddress } = useSelector( transactionDataSelector, ); @@ -215,10 +213,9 @@ export const SendTo = ({ previous }: { previous: ROUTES }) => { getDestinationBalances({ publicKey, networkDetails, - sorobanClient, }), ); - }, [dispatch, validatedPubKey, networkDetails, sorobanClient]); + }, [dispatch, validatedPubKey, networkDetails]); return ( diff --git a/extension/src/popup/components/signTransaction/Operations/KeyVal/index.tsx b/extension/src/popup/components/signTransaction/Operations/KeyVal/index.tsx index 5c17209ed..60d828150 100644 --- a/extension/src/popup/components/signTransaction/Operations/KeyVal/index.tsx +++ b/extension/src/popup/components/signTransaction/Operations/KeyVal/index.tsx @@ -14,6 +14,7 @@ import { import { CLAIM_PREDICATES } from "constants/transaction"; import { KeyIdenticon } from "popup/components/identicons/KeyIdenticon"; +import { CopyValue } from "popup/components/CopyValue"; import { truncateString } from "helpers/stellar"; import { formattedBuffer } from "popup/helpers/formatters"; @@ -32,8 +33,8 @@ export const KeyValueList = ({ operationValue: string | number | React.ReactNode; }) => (
-
{operationKey}
-
{operationValue}
+
{operationKey}
+
{operationValue}
); @@ -386,7 +387,14 @@ export const KeyValueInvokeHostFnArgs = ({ args }: { args: xdr.ScVal[] }) => (
{args.map((arg) => (
- {scValByType(arg)} + {arg.switch() === xdr.ScValType.scvAddress() ? ( + + ) : ( + scValByType(arg) + )}
))}
@@ -534,7 +542,12 @@ export const KeyValueInvokeHostFn = ({ /> + } /> .Operations__pair--key { + width: 200px; + text-overflow: ellipsis; + } + + & > .Operations__pair--value { + width: 200px; + flex-grow: 1; + display: flex; + justify-content: flex-end; + } + &--invoke { flex-direction: column; align-items: baseline; diff --git a/extension/src/popup/constants/metricsNames.ts b/extension/src/popup/constants/metricsNames.ts index eb06d38d7..61169d61c 100644 --- a/extension/src/popup/constants/metricsNames.ts +++ b/extension/src/popup/constants/metricsNames.ts @@ -32,6 +32,7 @@ export const METRIC_NAMES = { viewSecurity: "loaded screen: security", viewManageConnectedApps: "loaded screen: manage connected apps", viewAbout: "loaded screen: about", + viewManageAssetsLists: "loaded screen: manage assets lists", viewSendPayment: "loaded screen: send payment", sendPaymentTo: "loaded screen: send payment to", @@ -85,6 +86,8 @@ export const METRIC_NAMES = { manageAssetRemoveAsset: "manage asset: remove asset", manageAssetError: "manage asset: error", + manageAssetListsModifyAssetList: "manage asset list: modify asset list", + accountCreatorSuccess: "account creator: create password: success", accountCreatorReject: "account creator: create password: error", diff --git a/extension/src/popup/constants/routes.ts b/extension/src/popup/constants/routes.ts index f71a98892..0ea29cacf 100644 --- a/extension/src/popup/constants/routes.ts +++ b/extension/src/popup/constants/routes.ts @@ -45,6 +45,9 @@ export enum ROUTES { security = "/settings/security", manageConnectedApps = "/settings/manageConnectedApps", leaveFeedback = "/settings/leave-feedback", + manageAssetsLists = "/settings/manage-assets-lists", + manageAssetsListsModifyAssetList = "/settings/manage-assets-lists/modify-asset-list", + manageAssets = "/manage-assets", addAsset = "/manage-assets/add-asset", searchAsset = "/manage-assets/search-asset", diff --git a/extension/src/popup/ducks/settings.ts b/extension/src/popup/ducks/settings.ts index 879863f69..420cc0091 100644 --- a/extension/src/popup/ducks/settings.ts +++ b/extension/src/popup/ducks/settings.ts @@ -13,12 +13,20 @@ import { addCustomNetwork as addCustomNetworkService, removeCustomNetwork as removeCustomNetworkService, editCustomNetwork as editCustomNetworkService, + addAssetsList as addAssetsListService, + modifyAssetsList as modifyAssetsListService, } from "@shared/api/internal"; import { + NETWORKS, NetworkDetails, DEFAULT_NETWORKS, MAINNET_NETWORK_DETAILS, } from "@shared/constants/stellar"; +import { + AssetsListItem, + AssetsLists, + DEFAULT_ASSETS_LISTS, +} from "@shared/constants/soroban/token"; import { Settings, IndexerSettings, SettingsState } from "@shared/api/types"; @@ -54,6 +62,7 @@ const indexerInitialState: IndexerSettings = { const initialState = { ...settingsInitialState, ...indexerInitialState, + assetsLists: DEFAULT_ASSETS_LISTS, }; export const loadSettings = createAsyncThunk("settings/loadSettings", () => @@ -177,6 +186,49 @@ export const editCustomNetwork = createAsyncThunk< editCustomNetworkService({ networkDetails, networkIndex }), ); +export const addAssetsList = createAsyncThunk< + { assetsLists: AssetsLists; error: string }, + { assetsList: AssetsListItem; network: NETWORKS }, + { rejectValue: ErrorMessage } +>("settings/addAssetsList", async ({ assetsList, network }, thunkApi) => { + const res = await addAssetsListService({ assetsList, network }); + + if (res.error) { + return thunkApi.rejectWithValue({ + errorMessage: res.error || "Unable to add asset list", + }); + } + + return res; +}); + +export const modifyAssetsList = createAsyncThunk< + { assetsLists: AssetsLists; error: string }, + { + assetsList: AssetsListItem; + network: NETWORKS; + isDeleteAssetsList: boolean; + }, + { rejectValue: ErrorMessage } +>( + "settings/modifyAssetsList", + async ({ assetsList, network, isDeleteAssetsList }, thunkApi) => { + const res = await modifyAssetsListService({ + assetsList, + network, + isDeleteAssetsList, + }); + + if (res.error) { + return thunkApi.rejectWithValue({ + errorMessage: res.error || "Unable to modify asset list", + }); + } + + return res; + }, +); + const settingsSlice = createSlice({ name: "settings", initialState, @@ -234,7 +286,12 @@ const settingsSlice = createSlice({ }); builder.addCase( loadSettings.fulfilled, - (state, action: PayloadAction) => { + ( + state, + action: PayloadAction< + Settings & IndexerSettings & { assetsLists: AssetsLists } + >, + ) => { const { allowList, isDataSharingAllowed, @@ -247,6 +304,7 @@ const settingsSlice = createSlice({ isSorobanPublicEnabled, isRpcHealthy, userNotification, + assetsLists, } = action?.payload || { ...initialState, }; @@ -264,6 +322,7 @@ const settingsSlice = createSlice({ isSorobanPublicEnabled, isRpcHealthy, userNotification, + assetsLists, settingsState: SettingsState.SUCCESS, }; }, @@ -372,6 +431,42 @@ const settingsSlice = createSlice({ }; }, ); + builder.addCase( + addAssetsList.fulfilled, + ( + state, + action: PayloadAction<{ + assetsLists: AssetsLists; + }>, + ) => { + const { assetsLists } = action?.payload || { + assetsLists: initialState.assetsLists, + }; + + return { + ...state, + assetsLists, + }; + }, + ); + builder.addCase( + modifyAssetsList.fulfilled, + ( + state, + action: PayloadAction<{ + assetsLists: AssetsLists; + }>, + ) => { + const { assetsLists } = action?.payload || { + assetsLists: initialState.assetsLists, + }; + + return { + ...state, + assetsLists, + }; + }, + ); }, }); @@ -380,7 +475,7 @@ export const { reducer } = settingsSlice; export const { clearSettingsError } = settingsSlice.actions; export const settingsSelector = (state: { - settings: Settings & IndexerSettings; + settings: Settings & IndexerSettings & { assetsLists: AssetsLists }; }) => state.settings; export const settingsDataSharingSelector = createSelector( diff --git a/extension/src/popup/ducks/token-payment.ts b/extension/src/popup/ducks/token-payment.ts index 9536334f8..8b3b148a0 100644 --- a/extension/src/popup/ducks/token-payment.ts +++ b/extension/src/popup/ducks/token-payment.ts @@ -3,9 +3,14 @@ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { ActionStatus, ErrorMessage } from "@shared/api/types"; import { INDEXER_URL } from "@shared/constants/mercury"; import { NetworkDetails } from "@shared/constants/stellar"; +import { SorobanRpcNotSupportedError } from "@shared/constants/errors"; import { transfer } from "@shared/helpers/soroban/token"; -import { isCustomNetwork, xlmToStroop } from "helpers/stellar"; -import { SorobanContextInterface } from "popup/SorobanContext"; +import { isCustomNetwork } from "@shared/helpers/stellar"; +import { xlmToStroop } from "helpers/stellar"; +import { + buildSorobanServer, + getNewTxBuilder, +} from "@shared/helpers/soroban/server"; export const simulateTokenPayment = createAsyncThunk< { @@ -22,7 +27,6 @@ export const simulateTokenPayment = createAsyncThunk< amount: number; }; networkDetails: NetworkDetails; - sorobanClient: SorobanContextInterface; transactionFee: string; }, { @@ -31,20 +35,19 @@ export const simulateTokenPayment = createAsyncThunk< >( "simulateTokenPayment", async ( - { - address, - publicKey, - memo, - params, - networkDetails, - sorobanClient, - transactionFee, - }, + { address, publicKey, memo, params, networkDetails, transactionFee }, thunkApi, ) => { try { if (isCustomNetwork(networkDetails)) { - const builder = await sorobanClient.newTxBuilder( + if (!networkDetails.sorobanRpcUrl) { + throw new SorobanRpcNotSupportedError(); + } + const server = buildSorobanServer(networkDetails.sorobanRpcUrl); + const builder = await getNewTxBuilder( + publicKey, + networkDetails, + server, xlmToStroop(transactionFee).toFixed(), ); @@ -54,7 +57,7 @@ export const simulateTokenPayment = createAsyncThunk< new XdrLargeInt("i128", params.amount).toI128(), // amount ]; const transaction = transfer(address, transferParams, memo, builder); - const simulationResponse = await sorobanClient.server.simulateTransaction( + const simulationResponse = await server.simulateTransaction( transaction, ); diff --git a/extension/src/popup/ducks/transactionSubmission.ts b/extension/src/popup/ducks/transactionSubmission.ts index 164fcfffc..8cd096c0d 100644 --- a/extension/src/popup/ducks/transactionSubmission.ts +++ b/extension/src/popup/ducks/transactionSubmission.ts @@ -38,15 +38,15 @@ import { import { NETWORKS, NetworkDetails } from "@shared/constants/stellar"; import { ConfigurableWalletType } from "@shared/constants/hardwareWallet"; +import { isCustomNetwork } from "@shared/helpers/stellar"; -import { getCanonicalFromAsset, isCustomNetwork } from "helpers/stellar"; +import { getCanonicalFromAsset } from "helpers/stellar"; import { METRICS_DATA } from "constants/localStorageTypes"; import { MetricsData, emitMetric } from "helpers/metrics"; import { METRIC_NAMES } from "popup/constants/metricsNames"; import { INDEXER_URL } from "@shared/constants/mercury"; import { horizonGetBestPath } from "popup/helpers/horizonGetBestPath"; import { hardwareSign } from "popup/helpers/hardwareConnect"; -import { SorobanContextInterface } from "popup/SorobanContext"; export const signFreighterTransaction = createAsyncThunk< { signedTransaction: string }, @@ -89,14 +89,13 @@ export const submitFreighterTransaction = createAsyncThunk< publicKey: string; signedXDR: string; networkDetails: NetworkDetails; - sorobanClient: SorobanContextInterface; }, { rejectValue: ErrorMessage; } >( "submitFreighterTransaction", - async ({ publicKey, signedXDR, networkDetails, sorobanClient }, thunkApi) => { + async ({ publicKey, signedXDR, networkDetails }, thunkApi) => { if (isCustomNetwork(networkDetails)) { try { const txRes = await internalSubmitFreighterTransaction({ @@ -104,9 +103,7 @@ export const submitFreighterTransaction = createAsyncThunk< networkDetails, }); - thunkApi.dispatch( - getAccountBalances({ publicKey, networkDetails, sorobanClient }), - ); + thunkApi.dispatch(getAccountBalances({ publicKey, networkDetails })); return txRes; } catch (e) { @@ -159,14 +156,13 @@ export const submitFreighterSorobanTransaction = createAsyncThunk< publicKey: string; signedXDR: string; networkDetails: NetworkDetails; - sorobanClient: SorobanContextInterface; }, { rejectValue: ErrorMessage; } >( "submitFreighterSorobanTransaction", - async ({ publicKey, signedXDR, networkDetails, sorobanClient }, thunkApi) => { + async ({ publicKey, signedXDR, networkDetails }, thunkApi) => { if (isCustomNetwork(networkDetails)) { try { const txRes = await internalSubmitFreighterSorobanTransaction({ @@ -174,9 +170,7 @@ export const submitFreighterSorobanTransaction = createAsyncThunk< networkDetails, }); - thunkApi.dispatch( - getAccountBalances({ publicKey, networkDetails, sorobanClient }), - ); + thunkApi.dispatch(getAccountBalances({ publicKey, networkDetails })); return txRes; } catch (e) { @@ -342,63 +336,51 @@ export const getAccountBalances = createAsyncThunk< { publicKey: string; networkDetails: NetworkDetails; - sorobanClient: SorobanContextInterface; }, { rejectValue: ErrorMessage } ->( - "getAccountBalances", - async ({ publicKey, networkDetails, sorobanClient }, thunkApi) => { - try { - let balances; - - if (isCustomNetwork(networkDetails)) { - balances = await internalGetAccountBalancesStandalone({ - publicKey, - networkDetails, - sorobanClientServer: sorobanClient.server, - sorobanClientTxBuilder: sorobanClient.newTxBuilder, - }); - } else { - balances = await internalgetAccountIndexerBalances( - publicKey, - networkDetails, - ); - } +>("getAccountBalances", async ({ publicKey, networkDetails }, thunkApi) => { + try { + let balances; - storeBalanceMetricData(publicKey, balances.isFunded || false); - return balances; - } catch (e) { - return thunkApi.rejectWithValue({ errorMessage: e as string }); + if (isCustomNetwork(networkDetails)) { + balances = await internalGetAccountBalancesStandalone({ + publicKey, + networkDetails, + }); + } else { + balances = await internalgetAccountIndexerBalances( + publicKey, + networkDetails, + ); } - }, -); + + storeBalanceMetricData(publicKey, balances.isFunded || false); + return balances; + } catch (e) { + return thunkApi.rejectWithValue({ errorMessage: e as string }); + } +}); export const getDestinationBalances = createAsyncThunk< AccountBalancesInterface, { publicKey: string; networkDetails: NetworkDetails; - sorobanClient: SorobanContextInterface; }, { rejectValue: ErrorMessage } ->( - "getDestinationBalances", - async ({ publicKey, networkDetails, sorobanClient }, thunkApi) => { - try { - if (isCustomNetwork(networkDetails)) { - return await internalGetAccountBalancesStandalone({ - publicKey, - networkDetails, - sorobanClientServer: sorobanClient.server, - sorobanClientTxBuilder: sorobanClient.newTxBuilder, - }); - } - return await internalgetAccountIndexerBalances(publicKey, networkDetails); - } catch (e) { - return thunkApi.rejectWithValue({ errorMessage: e as string }); +>("getDestinationBalances", async ({ publicKey, networkDetails }, thunkApi) => { + try { + if (isCustomNetwork(networkDetails)) { + return await internalGetAccountBalancesStandalone({ + publicKey, + networkDetails, + }); } - }, -); + return await internalgetAccountIndexerBalances(publicKey, networkDetails); + } catch (e) { + return thunkApi.rejectWithValue({ errorMessage: e as string }); + } +}); export const getAssetIcons = createAsyncThunk< AssetIcons, diff --git a/extension/src/popup/helpers/__tests__/searchAsset.test.js b/extension/src/popup/helpers/__tests__/searchAsset.test.js index 8efa91122..03deac763 100644 --- a/extension/src/popup/helpers/__tests__/searchAsset.test.js +++ b/extension/src/popup/helpers/__tests__/searchAsset.test.js @@ -1,6 +1,32 @@ import * as SearchAsset from "../searchAsset"; +const validAssetList = { + name: "PiyalBasu Top 50", + provider: "PiyalBasu", + description: "Test asset list schema", + version: "1.0", + network: "public", + feedback: "https://piyalbasu.org", + assets: [ + { + code: "yXLM", + issuer: "GARDNV3Q7YGT4AKSDF25LT32YSCCW4EV22Y2TV3I2PU2MMXJTEDL5T55", + contract: "CBZVSNVB55ANF24QVJL2K5QCLOAB6XITGTGXYEAF6NPTXYKEJUYQOHFC", + name: "yXLM", + org: "Ultra Capital LLC dba Ultra Capital", + domain: "ultracapital.xyz", + icon: + "https://ipfs.io/ipfs/bafkreihntcz2lpaxawmbhwidtuifladkgew6olwuly2dz5pewqillhhpay", + decimals: 7, + }, + ], +}; + describe("searchAsset", () => { + beforeEach(() => { + jest.restoreAllMocks(); + }); + it("should getNativeContractDetails for Mainnet", () => { expect( SearchAsset.getNativeContractDetails({ network: "PUBLIC" }), @@ -40,4 +66,46 @@ describe("searchAsset", () => { issuer: "", }); }); + it("schemaValidatedAssetList should return list if valid", async () => { + const v = await SearchAsset.schemaValidatedAssetList(validAssetList); + expect(v).toStrictEqual(validAssetList); + }); + it("schemaValidatedAssetList should return empty list if schema fetch fails", async () => { + jest.spyOn(global, "fetch").mockImplementation(() => + Promise.resolve({ + ok: false, + }), + ); + const v = await SearchAsset.schemaValidatedAssetList(validAssetList); + expect(v).toStrictEqual({ assets: [] }); + }); + it("schemaValidatedAssetList should return empty list and errors if validation fails", async () => { + const v = await SearchAsset.schemaValidatedAssetList({ + // incorrect key + title: "PiyalBasu Top 50", + + provider: "PiyalBasu", + description: "Test asset list schema", + version: "1.0", + network: "public", + feedback: "https://piyalbasu.org", + assets: [ + { + code: "yXLM", + issuer: "GARDNV3Q7YGT4AKSDF25LT32YSCCW4EV22Y2TV3I2PU2MMXJTEDL5T55", + contract: "CBZVSNVB55ANF24QVJL2K5QCLOAB6XITGTGXYEAF6NPTXYKEJUYQOHFC", + name: "yXLM", + org: "Ultra Capital LLC dba Ultra Capital", + domain: "ultracapital.xyz", + icon: + "https://ipfs.io/ipfs/bafkreihntcz2lpaxawmbhwidtuifladkgew6olwuly2dz5pewqillhhpay", + decimals: 7, + }, + ], + }); + expect(v.assets).toStrictEqual([]); + + // error for missing `name` and error for additional key `title` + expect(v.errors).toHaveLength(2); + }); }); diff --git a/extension/src/popup/helpers/searchAsset.ts b/extension/src/popup/helpers/searchAsset.ts index a60e6fa9d..49f8e1bdb 100644 --- a/extension/src/popup/helpers/searchAsset.ts +++ b/extension/src/popup/helpers/searchAsset.ts @@ -1,6 +1,8 @@ import { captureException } from "@sentry/browser"; -import { fetchAssetList } from "@stellar-asset-lists/sdk"; +import { validate } from "jsonschema"; 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 ({ @@ -22,6 +24,34 @@ export const searchAsset = async ({ } }; +export const schemaValidatedAssetList = async (assetListJson: any) => { + let schemaRes; + try { + schemaRes = await fetch( + "https://raw.githubusercontent.com/orbitlens/stellar-protocol/sep-0042-token-lists/contents/sep-0042/assetlist.schema.json", + ); + } catch (err) { + captureException("Error fetching SEP-0042 JSON schema"); + return { assets: [] }; + } + + if (!schemaRes.ok) { + captureException("Unable to fetch SEP-0042 JSON schema"); + return { assets: [] }; + } + + const schemaResJson = await schemaRes?.json(); + + // check against the SEP-0042 schema + const validatedList = validate(assetListJson, schemaResJson); + + if (validatedList.errors.length) { + return { assets: [], errors: validatedList.errors }; + } + + return assetListJson; +}; + export const getNativeContractDetails = (networkDetails: NetworkDetails) => { const NATIVE_CONTRACT_DEFAULTS = { code: "XLM", @@ -48,34 +78,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 +88,86 @@ 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 = "", isEnabled } = networkList; + + if (isEnabled) { + const fetchAndParse = async () => { + let res; + try { + res = await fetch(url); + } catch (e) { + captureException(`Failed to load asset list: ${url}`); } - }, - }); - verifiedTokens = verifiedTokenRes.filter((record: TokenRecord) => { - const regex = new RegExp(contractId, "i"); - if (record.contract.match(regex)) { - return true; + return res?.json(); + }; + + promiseArr.push(fetchAndParse()); + } + } + + const promiseRes = await Promise.allSettled(promiseArr); + + const verifiedTokens = [] as VerifiedTokenRecord[]; + + let verifiedToken = {} as TokenRecord; + const verifiedLists: string[] = []; + + // eslint-disable-next-line no-restricted-syntax + for (const r of promiseRes) { + if (r.status === "fulfilled") { + // confirm that this list still adheres to the agreed upon schema + const validatedList = await schemaValidatedAssetList(r.value); + const list = validatedList?.tokens + ? validatedList?.tokens + : validatedList?.assets; + if (list) { + // eslint-disable-next-line no-restricted-syntax + for (const record of list) { + const regex = new RegExp(contractId, "i"); + if (record.contract && record.contract.match(regex)) { + verifiedToken = record; + verifiedLists.push(r.value.name as string); + break; + } + } } - 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/helpers/useSetupSigningFlow.ts b/extension/src/popup/helpers/useSetupSigningFlow.ts index 261a81220..cfacd6276 100644 --- a/extension/src/popup/helpers/useSetupSigningFlow.ts +++ b/extension/src/popup/helpers/useSetupSigningFlow.ts @@ -55,7 +55,6 @@ export function useSetupSigningFlow( }; const signAndClose = async () => { - emitMetric(METRIC_NAMES.approveSign); if (isHardwareWallet) { dispatch( startHwSign({ transactionXDR: transactionXdr, shouldSubmit: false }), @@ -63,6 +62,7 @@ export function useSetupSigningFlow( setStartedHwSign(true); } else { await dispatch(signFn()); + await emitMetric(METRIC_NAMES.approveSign); window.close(); } }; diff --git a/extension/src/popup/locales/en/translation.json b/extension/src/popup/locales/en/translation.json index 5c0236066..d9557a085 100644 --- a/extension/src/popup/locales/en/translation.json +++ b/extension/src/popup/locales/en/translation.json @@ -25,8 +25,10 @@ "Add custom network": "Add custom network", "Add Custom Network": "Add Custom Network", "Add it manually": "Add it manually", + "Add list": "Add list", "Add network": "Add network", "Add New Address": "Add New Address", + "Add new list": "Add new list", "Add Soroban token": "Add Soroban token", "Address": "Address", "Addresses are uppercase and begin with letters “G“ or “M“": "Addresses are uppercase and begin with letters “G“ or “M“.", @@ -50,6 +52,7 @@ "Approve and review next": "Approve and review next", "Approve using": "Approve using", "are required to add a new asset": "are required to add a new asset.", + "Are you sure you want to delete this list?": "Are you sure you want to delete this list?", "Are you sure you want to remove this network? You will have to re-add it if you want to use it again": "Are you sure you want to remove this network? You will have to re-add it if you want to use it again.", "As long as you have your old and new mnemonics phrase, you’ll still be able to control accounts related to your current backup phrase which were not merged": { " For that, you’ll need to import your current backup phrase into Freighter (Freighter supports one backup phrase imported at a time)": "As long as you have your old and new mnemonics phrase, you’ll still be able to control accounts related to your current backup phrase which were not merged. For that, you’ll need to import your current backup phrase into Freighter (Freighter supports one backup phrase imported at a time)." @@ -61,6 +64,7 @@ "Asset domain": "Asset domain", "Asset home domain doesn’t exist, TOML file format is invalid, or asset doesn't match currency description": "Asset home domain doesn’t exist, TOML file format is invalid, or asset doesn't match currency description", "Asset Info": "Asset Info", + "Asset lists": "Asset lists", "Asset not found": "Asset not found", "Assets found in this domain": "Assets found in this domain", "At least one uppercase letter": "At least one uppercase letter", @@ -78,15 +82,13 @@ "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", "Bump To": "Bump To", "Buy Amount": "Buy Amount", "Buying": "Buying", + "by": "by", "Can’t find the asset you’re looking for?": "Can’t find the asset you’re looking for?", "Cancel": "Cancel", "Check the destination account memo requirements and include it in the transaction": "Check the destination account memo requirements and include it in the transaction.", @@ -98,6 +100,7 @@ "Close": "Close", "Confirm": "Confirm", "Confirm Data": "Confirm Data", + "Confirm delete": "Confirm delete", "Confirm password": "Confirm password", "Confirm removing Network": "Confirm removing Network", "Confirm Send": "Confirm Send", @@ -120,6 +123,7 @@ "Custom": "Custom", "data entries": "data entries", "Date": "Date", + "Delete": "Delete", "Destination": "Destination", "Destination account does not accept this asset": "Destination account does not accept this asset", "Destination account doesn’t exist": "Destination account doesn’t exist", @@ -133,12 +137,16 @@ "digits after the decimal allowed": "digits after the decimal allowed", "directory": "directory", "disable": "disable", + "Disabled": "Disabled", "Done": "Done", "Double check the domain name": { " If it is incorrect in any way, do not share your public key and contact the site administrator via a verified email or social media account to confirm that this domain is correct": "Double check the domain name. If it is incorrect in any way, do not share your public key and contact the site administrator via a verified email or social media account to confirm that this domain is correct." }, "enable": "enable", "Enable experimental mode": "Enable experimental mode", + "Enable this list": "Enable this list", + "Enabled": "Enabled", + "Enter a Stellar Asset List compatible URL": "Enter a Stellar Asset List compatible URL", "Enter Friendbot URL": "Enter Friendbot URL", "Enter network name": "Enter network name", "Enter network URL": "Enter network URL", @@ -163,6 +171,7 @@ "Fees can vary depending on the network congestion": { " Please try using the suggested fee and try again": "Fees can vary depending on the network congestion. Please try using the suggested fee and try again." }, + "Fetch list information": "Fetch list information", "Finish": "Finish", "For your safety, signing this transaction is disabled": "For your safety, signing this transaction is disabled", "For your security, we'll check if you got it right in the next step": "For your security, we'll check if you got it right in the next step.", @@ -179,6 +188,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." @@ -189,7 +201,6 @@ "Fund with Friendbot": "Fund with Friendbot", "Got it": "Got it", "Hash": "Hash", - "Have a suggestion or bug report? Create an issue on Github": "Have a suggestion or bug report? Create an issue on Github", "Help": "Help", "High Threshold": "High Threshold", "History": "History", @@ -206,6 +217,7 @@ "I’m new!": "I’m new!", "I’ve done this before": "I’ve done this before", "If you believe you have interacted with this domain before, it is possible that scammers have copied the original site and/or made small changes to the domain name, and that this site is a scam": "If you believe you have interacted with this domain before, it is possible that scammers have copied the original site and/or made small changes to the domain name, and that this site is a scam.", + "If you delete this list, you will have to re-add it manually": "If you delete this list, you will have to re-add it manually.", "Import": "Import", "Import a Stellar secret key": "Import a Stellar secret key", "Import Stellar Secret Key": "Import Stellar Secret Key", @@ -228,7 +240,7 @@ "is requesting approval to sign a blob of data": "is requesting approval to sign a blob of data", "is requesting approval to sign an authorization entry": "is requesting approval to sign an authorization entry", "Issuer": "Issuer", - "Join the #wallets Discord channel for updates and questions": "Join the #wallets Discord channel for updates and questions", + "Join community Discord": "Join community Discord", "Keep it in a safe place": "Keep it in a safe place.", "Keep your account safe": "Keep your account safe", "Keep your recovery phrase in a safe and secure place": { @@ -298,6 +310,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", @@ -307,9 +320,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." @@ -341,6 +356,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. " @@ -359,6 +375,7 @@ }, "Remove": "Remove", "Remove trustline": "Remove trustline", + "Report issue on Github": "Report issue on Github", "Reserved Balance*": "Reserved Balance*", "Resource cost": "Resource cost", "Review": "Review", @@ -424,7 +441,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.", @@ -442,8 +458,14 @@ "this alert by going to": "this alert by going to", "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." }, @@ -456,6 +478,7 @@ "To create a new account you need to send at least 1 XLM to it": "To create a new account you need to send at least 1 XLM to it.", "To create this account, fund it with a minimum of 1 XLM": "To create this account, fund it with a minimum of 1 XLM.", "Token ID": "Token ID", + "Token List URL": "Token List URL", "Total Available": "Total Available", "Total Balance": "Total Balance", "Trading or sending this asset is not recommended": { @@ -472,9 +495,11 @@ "Type": "Type", "Unable to connect to": "Unable to connect to", "Unable to migrate": "Unable to migrate", + "Unable to parse assets lists": "Unable to parse assets lists", "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 9e78c8423..24c9dbf5e 100644 --- a/extension/src/popup/locales/pt/translation.json +++ b/extension/src/popup/locales/pt/translation.json @@ -25,8 +25,10 @@ "Add custom network": "Add custom network", "Add Custom Network": "Add Custom Network", "Add it manually": "Add it manually", + "Add list": "Add list", "Add network": "Add network", "Add New Address": "Add New Address", + "Add new list": "Add new list", "Add Soroban token": "Add Soroban token", "Address": "Address", "Addresses are uppercase and begin with letters “G“ or “M“": "Addresses are uppercase and begin with letters “G“ or “M“.", @@ -50,6 +52,7 @@ "Approve and review next": "Approve and review next", "Approve using": "Approve using", "are required to add a new asset": "are required to add a new asset.", + "Are you sure you want to delete this list?": "Are you sure you want to delete this list?", "Are you sure you want to remove this network? You will have to re-add it if you want to use it again": "Are you sure you want to remove this network? You will have to re-add it if you want to use it again.", "As long as you have your old and new mnemonics phrase, you’ll still be able to control accounts related to your current backup phrase which were not merged": { " For that, you’ll need to import your current backup phrase into Freighter (Freighter supports one backup phrase imported at a time)": "As long as you have your old and new mnemonics phrase, you’ll still be able to control accounts related to your current backup phrase which were not merged. For that, you’ll need to import your current backup phrase into Freighter (Freighter supports one backup phrase imported at a time)." @@ -61,6 +64,7 @@ "Asset domain": "Asset domain", "Asset home domain doesn’t exist, TOML file format is invalid, or asset doesn't match currency description": "Asset home domain doesn’t exist, TOML file format is invalid, or asset doesn't match currency description", "Asset Info": "Asset Info", + "Asset lists": "Asset lists", "Asset not found": "Asset not found", "Assets found in this domain": "Assets found in this domain", "At least one uppercase letter": "At least one uppercase letter", @@ -78,15 +82,13 @@ "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", "Bump To": "Bump To", "Buy Amount": "Buy Amount", "Buying": "Buying", + "by": "by", "Can’t find the asset you’re looking for?": "Can’t find the asset you’re looking for?", "Cancel": "Cancel", "Check the destination account memo requirements and include it in the transaction": "Check the destination account memo requirements and include it in the transaction.", @@ -98,6 +100,7 @@ "Close": "Close", "Confirm": "Confirm", "Confirm Data": "Confirm Data", + "Confirm delete": "Confirm delete", "Confirm password": "Confirm password", "Confirm removing Network": "Confirm removing Network", "Confirm Send": "Confirm Send", @@ -120,6 +123,7 @@ "Custom": "Custom", "data entries": "data entries", "Date": "Date", + "Delete": "Delete", "Destination": "Destination", "Destination account does not accept this asset": "Destination account does not accept this asset", "Destination account doesn’t exist": "Destination account doesn’t exist", @@ -133,12 +137,16 @@ "digits after the decimal allowed": "digits after the decimal allowed", "directory": "directory", "disable": "disable", + "Disabled": "Disabled", "Done": "Done", "Double check the domain name": { " If it is incorrect in any way, do not share your public key and contact the site administrator via a verified email or social media account to confirm that this domain is correct": "Double check the domain name. If it is incorrect in any way, do not share your public key and contact the site administrator via a verified email or social media account to confirm that this domain is correct." }, "enable": "enable", "Enable experimental mode": "Enable experimental mode", + "Enable this list": "Enable this list", + "Enabled": "Enabled", + "Enter a Stellar Asset List compatible URL": "Enter a Stellar Asset List compatible URL", "Enter Friendbot URL": "Enter Friendbot URL", "Enter network name": "Enter network name", "Enter network URL": "Enter network URL", @@ -163,6 +171,7 @@ "Fees can vary depending on the network congestion": { " Please try using the suggested fee and try again": "Fees can vary depending on the network congestion. Please try using the suggested fee and try again." }, + "Fetch list information": "Fetch list information", "Finish": "Finish", "For your safety, signing this transaction is disabled": "For your safety, signing this transaction is disabled", "For your security, we'll check if you got it right in the next step": "For your security, we'll check if you got it right in the next step.", @@ -179,6 +188,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." @@ -189,7 +201,6 @@ "Fund with Friendbot": "Fund with Friendbot", "Got it": "Got it", "Hash": "Hash", - "Have a suggestion or bug report? Create an issue on Github": "Have a suggestion or bug report? Create an issue on Github", "Help": "Help", "High Threshold": "High Threshold", "History": "History", @@ -206,6 +217,7 @@ "I’m new!": "I’m new!", "I’ve done this before": "I’ve done this before", "If you believe you have interacted with this domain before, it is possible that scammers have copied the original site and/or made small changes to the domain name, and that this site is a scam": "If you believe you have interacted with this domain before, it is possible that scammers have copied the original site and/or made small changes to the domain name, and that this site is a scam.", + "If you delete this list, you will have to re-add it manually": "If you delete this list, you will have to re-add it manually.", "Import": "Import", "Import a Stellar secret key": "Import a Stellar secret key", "Import Stellar Secret Key": "Import Stellar Secret Key", @@ -228,7 +240,7 @@ "is requesting approval to sign a blob of data": "is requesting approval to sign a blob of data", "is requesting approval to sign an authorization entry": "is requesting approval to sign an authorization entry", "Issuer": "Issuer", - "Join the #wallets Discord channel for updates and questions": "Join the #wallets Discord channel for updates and questions", + "Join community Discord": "Join community Discord", "Keep it in a safe place": "Keep it in a safe place.", "Keep your account safe": "Keep your account safe", "Keep your recovery phrase in a safe and secure place": { @@ -298,6 +310,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", @@ -307,9 +320,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." @@ -341,6 +356,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. " @@ -359,6 +375,7 @@ }, "Remove": "Remove", "Remove trustline": "Remove trustline", + "Report issue on Github": "Report issue on Github", "Reserved Balance*": "Reserved Balance*", "Resource cost": "Resource cost", "Review": "Review", @@ -424,7 +441,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.", @@ -442,8 +458,14 @@ "this alert by going to": "this alert by going to", "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." }, @@ -456,6 +478,7 @@ "To create a new account you need to send at least 1 XLM to it": "To create a new account you need to send at least 1 XLM to it.", "To create this account, fund it with a minimum of 1 XLM": "To create this account, fund it with a minimum of 1 XLM.", "Token ID": "Token ID", + "Token List URL": "Token List URL", "Total Available": "Total Available", "Total Balance": "Total Balance", "Trading or sending this asset is not recommended": { @@ -472,9 +495,11 @@ "Type": "Type", "Unable to connect to": "Unable to connect to", "Unable to migrate": "Unable to migrate", + "Unable to parse assets lists": "Unable to parse assets lists", "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/metrics/views.ts b/extension/src/popup/metrics/views.ts index 6d402c04d..f6cda6a1b 100644 --- a/extension/src/popup/metrics/views.ts +++ b/extension/src/popup/metrics/views.ts @@ -65,6 +65,9 @@ const routeToEventName = { [ROUTES.editNetwork]: METRIC_NAMES.viewEditNetwork, [ROUTES.networkSettings]: METRIC_NAMES.viewNetworkSettings, [ROUTES.leaveFeedback]: METRIC_NAMES.viewLeaveFeedback, + [ROUTES.manageAssetsLists]: METRIC_NAMES.viewManageAssetsLists, + [ROUTES.manageAssetsListsModifyAssetList]: + METRIC_NAMES.manageAssetListsModifyAssetList, [ROUTES.accountMigration]: METRIC_NAMES.viewAccountMigration, [ROUTES.accountMigrationReviewMigration]: METRIC_NAMES.viewAccountMigrationReviewMigration, diff --git a/extension/src/popup/react-app-env.d.ts b/extension/src/popup/react-app-env.d.ts index 9a338fb19..a10db3913 100755 --- a/extension/src/popup/react-app-env.d.ts +++ b/extension/src/popup/react-app-env.d.ts @@ -1,7 +1,7 @@ // declare module "*.png"; -declare module "*.svg" { +declare module "*.svg?react" { import React = require("react"); export const ReactComponent: React.SFC>; diff --git a/extension/src/popup/styles/utils.scss b/extension/src/popup/styles/utils.scss index 9486e6b80..6e1b8b9ab 100644 --- a/extension/src/popup/styles/utils.scss +++ b/extension/src/popup/styles/utils.scss @@ -10,3 +10,10 @@ $base-font-size: 16px; @function pxToRem($pxValue) { @return #{math.div(removePxUnit($pxValue), removePxUnit($base-font-size))}rem; } + +@mixin native-like-outline { + outline-color: rgb(153, 200, 255); + outline-offset: 1px; + outline-style: auto; + outline-width: 1px; +} diff --git a/extension/src/popup/views/Account/index.tsx b/extension/src/popup/views/Account/index.tsx index d5c6ca35e..cc020c058 100644 --- a/extension/src/popup/views/Account/index.tsx +++ b/extension/src/popup/views/Account/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState, useEffect } from "react"; +import React, { useState, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; import { Button, @@ -9,10 +9,7 @@ import { } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; -import { - getAccountHistoryStandalone, - getIndexerAccountHistory, -} from "@shared/api/internal"; +import { getAccountHistory } from "@shared/api/internal"; import { AccountBalancesInterface, ActionStatus, @@ -48,7 +45,7 @@ import { sortBalances, sortOperationsByAsset, } from "popup/helpers/account"; -import { isCustomNetwork, truncatedPublicKey } from "helpers/stellar"; +import { truncatedPublicKey } from "helpers/stellar"; import { navigateTo } from "popup/helpers/navigate"; import { AccountAssets } from "popup/components/account/AccountAssets"; import { AccountHeader } from "popup/components/account/AccountHeader"; @@ -58,8 +55,6 @@ import { NotFundedMessage } from "popup/components/account/NotFundedMessage"; import "popup/metrics/authServices"; -import { SorobanContext } from "../../SorobanContext"; - import "./styles.scss"; export const defaultAccountBalances = { @@ -89,8 +84,6 @@ export const Account = () => { const [selectedAsset, setSelectedAsset] = useState(""); const [isLoading, setLoading] = useState(true); - const sorobanClient = useContext(SorobanContext); - const { balances, isFunded, error } = accountBalances; useEffect(() => { @@ -102,7 +95,6 @@ export const Account = () => { getAccountBalances({ publicKey, networkDetails, - sorobanClient, }), ); dispatch(getBlockedDomains()); @@ -110,13 +102,7 @@ export const Account = () => { return () => { dispatch(resetAccountBalanceStatus()); }; - }, [ - publicKey, - networkDetails, - isAccountFriendbotFunded, - sorobanClient, - dispatch, - ]); + }, [publicKey, networkDetails, isAccountFriendbotFunded, dispatch]); useEffect(() => { if (!balances) { @@ -135,18 +121,7 @@ export const Account = () => { const fetchAccountHistory = async () => { try { - let operations = []; - if (isCustomNetwork(networkDetails)) { - operations = await getAccountHistoryStandalone({ - publicKey, - networkDetails, - }); - } else { - operations = await getIndexerAccountHistory({ - publicKey, - networkDetails, - }); - } + const operations = await getAccountHistory(publicKey, networkDetails); setAssetOperations( sortOperationsByAsset({ operations, diff --git a/extension/src/popup/views/AccountCreator/index.tsx b/extension/src/popup/views/AccountCreator/index.tsx index bb3dcae75..427318620 100644 --- a/extension/src/popup/views/AccountCreator/index.tsx +++ b/extension/src/popup/views/AccountCreator/index.tsx @@ -121,12 +121,18 @@ export const AccountCreator = () => { - {({ field }: FieldProps) => ( + {({ field, form }: FieldProps) => ( { + if (e.key === "Enter") { + form.setFieldValue("termsOfUse", !field.value); + e.currentTarget.checked = !field.value; + } + }} label={ <> {t("I have read and agree to")}{" "} diff --git a/extension/src/popup/views/AccountCreator/styles.scss b/extension/src/popup/views/AccountCreator/styles.scss index 0f730aea0..3873b7bd1 100644 --- a/extension/src/popup/views/AccountCreator/styles.scss +++ b/extension/src/popup/views/AccountCreator/styles.scss @@ -1,3 +1,5 @@ +@use "../../styles/utils.scss" as *; + .AccountCreator { margin-bottom: 1rem; @@ -20,3 +22,13 @@ justify-content: center; } } + +.Onboarding { + .Checkbox__container { + input[type="checkbox"]:focus + label { + span { + @include native-like-outline; + } + } + } +} diff --git a/extension/src/popup/views/AccountHistory/index.tsx b/extension/src/popup/views/AccountHistory/index.tsx index c43c3e906..e94307b57 100644 --- a/extension/src/popup/views/AccountHistory/index.tsx +++ b/extension/src/popup/views/AccountHistory/index.tsx @@ -4,10 +4,7 @@ import { useTranslation } from "react-i18next"; import { Loader } from "@stellar/design-system"; import { Horizon } from "stellar-sdk"; -import { - getAccountHistoryStandalone, - getIndexerAccountHistory, -} from "@shared/api/internal"; +import { getAccountHistory } from "@shared/api/internal"; import { ActionStatus } from "@shared/api/types"; import { SorobanTokenInterface } from "@shared/constants/soroban/token"; @@ -20,7 +17,6 @@ import { getStellarExpertUrl, } from "popup/helpers/account"; import { getAttrsFromSorobanHorizonOp } from "popup/helpers/soroban"; -import { isCustomNetwork } from "helpers/stellar"; import { historyItemDetailViewProps, @@ -130,18 +126,7 @@ export const AccountHistory = () => { const fetchAccountHistory = async () => { try { - let operations = [] as Horizon.ServerApi.OperationRecord[]; - if (isCustomNetwork(networkDetails)) { - operations = await getAccountHistoryStandalone({ - publicKey, - networkDetails, - }); - } else { - operations = await getIndexerAccountHistory({ - publicKey, - networkDetails, - }); - } + const operations = await getAccountHistory(publicKey, networkDetails); setHistorySegments(createSegments(operations)); } catch (e) { console.error(e); diff --git a/extension/src/popup/views/LeaveFeedback/index.tsx b/extension/src/popup/views/LeaveFeedback/index.tsx index 5ed30d47a..fd62fef73 100644 --- a/extension/src/popup/views/LeaveFeedback/index.tsx +++ b/extension/src/popup/views/LeaveFeedback/index.tsx @@ -1,77 +1,34 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import { Button, Icon } from "@stellar/design-system"; +import { Icon } from "@stellar/design-system"; import { SubviewHeader } from "popup/components/SubviewHeader"; import { View } from "popup/basics/layout/View"; +import { ListNavLink, ListNavLinkWrapper } from "popup/basics/ListNavLink"; import "./styles.scss"; export const LeaveFeedback = () => { const { t } = useTranslation(); - const openLink = (link: string) => { - window.open(link, "_blank"); - return false; - }; - return ( -
-
- {/* Discord */} -
-
- -
-
- {t( - "Join the #wallets Discord channel for updates and questions", - )} -
- - -
- - {/* GitHub */} -
-
- -
-
- {t( - "Have a suggestion or bug report? Create an issue on Github", - )} -
- - -
-
-
+ + } + href="https://discord.com/channels/897514728459468821/1019346446014759013" + > + {t("Join community Discord")} + + } + href="https://github.com/stellar/freighter/issues" + > + {t("Report issue on Github")} + +
); diff --git a/extension/src/popup/views/LeaveFeedback/styles.scss b/extension/src/popup/views/LeaveFeedback/styles.scss index 5c63e0379..e69de29bb 100644 --- a/extension/src/popup/views/LeaveFeedback/styles.scss +++ b/extension/src/popup/views/LeaveFeedback/styles.scss @@ -1,44 +0,0 @@ -.LeaveFeedback { - display: flex; - flex-direction: column; - font-size: 0.875rem; - line-height: 1.375rem; - - &__itemsContainer { - display: flex; - flex-direction: column; - gap: 1.5rem; - - & > :not(:first-child) { - border-top: 1px solid var(--color-gray-40); - padding-top: 1.5rem; - } - } - - &__item { - display: flex; - flex-direction: column; - gap: 0.75rem; - - &__icon { - width: 2.5rem; - height: 2.5rem; - border-radius: 2.5rem; - border: 1px solid var(--color-gray-40); - background-color: var(--color-gray-10); - display: flex; - align-items: center; - justify-content: center; - - svg { - display: block; - width: 1.5rem; - height: 1.5rem; - } - } - - .Button { - width: fit-content; - } - } -} diff --git a/extension/src/popup/views/ManageAssetsLists/index.tsx b/extension/src/popup/views/ManageAssetsLists/index.tsx new file mode 100644 index 000000000..2e7e3f23b --- /dev/null +++ b/extension/src/popup/views/ManageAssetsLists/index.tsx @@ -0,0 +1,128 @@ +import React, { useEffect, useState } from "react"; +import { useSelector } from "react-redux"; +import { Switch } from "react-router-dom"; +import { captureException } from "@sentry/browser"; +import { useTranslation } from "react-i18next"; + +import { ROUTES } from "popup/constants/routes"; +import { NETWORKS } from "@shared/constants/stellar"; +import { AssetsListKey } from "@shared/constants/soroban/token"; +import { settingsSelector } from "popup/ducks/settings"; +import { PublicKeyRoute } from "popup/Router"; + +import { AssetLists } from "popup/components/manageAssetsLists/AssetLists"; +import { ModifyAssetList } from "popup/components/manageAssetsLists/ModifyAssetList"; + +import "./styles.scss"; + +export interface AssetsListsData { + url: string; + name: string; + provider: string; + description: string; + isEnabled: boolean; +} + +export interface SortedAssetsListsData { + enabled: AssetsListsData[]; + disabled: AssetsListsData[]; +} + +export const ManageAssetsLists = () => { + const [selectedNetwork, setSelectedNetwork] = useState("" as AssetsListKey); + const [assetsListsData, setAssetsListsData] = useState( + [] as AssetsListsData[], + ); + const [sortedAssetsListsData, setSortedAssetsListsData] = useState({ + enabled: [], + disabled: [], + } as SortedAssetsListsData); + const [isLoading, setIsLoading] = useState(true); + const { assetsLists, networkDetails } = useSelector(settingsSelector); + const { t } = useTranslation(); + + useEffect(() => { + if (!selectedNetwork) { + return; + } + const networkLists = assetsLists[selectedNetwork] || []; + const listsArr: AssetsListsData[] = []; + + const fetchLists = async () => { + setIsLoading(true); + + // TODO: make these calls concurrent + // eslint-disable-next-line no-restricted-syntax + for (const networkList of networkLists) { + const { url = "", isEnabled } = networkList; + try { + const res = await fetch(url); + const resJson: AssetsListsData = await res.json(); + resJson.url = url; + resJson.isEnabled = isEnabled; + listsArr.push(resJson); + } catch (e) { + captureException(`Failed to load asset list: ${url}`); + } + } + + setAssetsListsData(listsArr); + setIsLoading(false); + }; + + fetchLists(); + }, [selectedNetwork, assetsLists]); + + useEffect(() => { + if (assetsListsData.length) { + const sortedList: SortedAssetsListsData = { + enabled: [], + disabled: [], + }; + assetsListsData.forEach((list) => { + if (list.isEnabled) { + sortedList.enabled.push(list); + } else { + sortedList.disabled.push(list); + } + }); + + setSortedAssetsListsData(sortedList); + } + }, [assetsListsData]); + + useEffect(() => { + setSelectedNetwork( + networkDetails.network === NETWORKS.TESTNET + ? NETWORKS.TESTNET + : NETWORKS.PUBLIC, + ); + }, [networkDetails]); + + const handleSelectChange = (e: React.ChangeEvent) => { + setSelectedNetwork(e.target.value as AssetsListKey); + }; + + return assetsLists ? ( + <> + + + + + + + + + + ) : ( +
{t("Unable to parse assets lists")}
+ ); +}; diff --git a/extension/src/popup/views/ManageAssetsLists/styles.scss b/extension/src/popup/views/ManageAssetsLists/styles.scss new file mode 100644 index 000000000..e69de29bb diff --git a/extension/src/popup/views/RecoverAccount/index.tsx b/extension/src/popup/views/RecoverAccount/index.tsx index a6c8a3d3c..66d9e4adb 100644 --- a/extension/src/popup/views/RecoverAccount/index.tsx +++ b/extension/src/popup/views/RecoverAccount/index.tsx @@ -264,7 +264,7 @@ export const RecoverAccount = () => { - {({ field }: FieldProps) => ( + {({ field, form }: FieldProps) => ( { ? errors.termsOfUse : null } + onKeyDown={(e) => { + if (e.key === "Enter") { + form.setFieldValue("termsOfUse", !field.value); + e.currentTarget.checked = !field.value; + } + }} label={ <> {t("I have read and agree to")}{" "} diff --git a/extension/src/popup/views/ReviewAuth/index.tsx b/extension/src/popup/views/ReviewAuth/index.tsx index 4ea12de79..fa26c558a 100644 --- a/extension/src/popup/views/ReviewAuth/index.tsx +++ b/extension/src/popup/views/ReviewAuth/index.tsx @@ -1,10 +1,9 @@ -import React, { useContext, useState } from "react"; +import React, { useState } from "react"; import { useLocation } from "react-router-dom"; import { captureException } from "@sentry/browser"; import BigNumber from "bignumber.js"; import { MemoType, - Address, Operation, Transaction, TransactionBuilder, @@ -14,8 +13,8 @@ import { useSelector } from "react-redux"; import { Button, Icon, Loader } from "@stellar/design-system"; import { decodeString } from "helpers/urls"; -import { INDEXER_URL } from "@shared/constants/mercury"; -import { getSorobanTokenBalance } from "@shared/api/internal"; +import { getTokenDetails } from "@shared/api/internal"; + import { PunycodedDomain } from "popup/components/PunycodedDomain"; import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; import { signTransaction, rejectTransaction } from "popup/ducks/access"; @@ -27,7 +26,7 @@ import { KeyValueList, } from "popup/components/signTransaction/Operations/KeyVal"; import { useTranslation } from "react-i18next"; -import { isCustomNetwork, truncateString } from "helpers/stellar"; +import { truncateString } from "helpers/stellar"; import { emitMetric } from "helpers/metrics"; import { FlaggedKeys } from "types/transactions"; import { @@ -51,8 +50,8 @@ import { } from "popup/components/WarningMessages"; import { METRIC_NAMES } from "popup/constants/metricsNames"; import { SorobanTokenIcon } from "popup/components/account/AccountAssets"; +import { CopyValue } from "popup/components/CopyValue"; import { OPERATION_TYPES } from "constants/transaction"; -import { SorobanContext } from "popup/SorobanContext"; import { Summary } from "../SignTransaction/Preview/Summary"; import { Details } from "../SignTransaction/Preview/Details"; import { Data } from "../SignTransaction/Preview/Data"; @@ -63,6 +62,7 @@ import "./styles.scss"; export const ReviewAuth = () => { const location = useLocation(); const { t } = useTranslation(); + const [isLoadingAuth, setLoadingAuth] = useState(true); const decodedSearchParam = decodeString(location.search.replace("?", "")); const params = decodedSearchParam ? JSON.parse(decodedSearchParam) : {}; @@ -101,6 +101,7 @@ export const ReviewAuth = () => { const isLastEntry = activeAuthEntryIndex + 1 === op.auth?.length; const reviewAuthEntry = () => { emitMetric(METRIC_NAMES.reviewedAuthEntry); + setLoadingAuth(true); if (isLastEntry) { setHasConfirmedAuth(true); } else { @@ -132,7 +133,11 @@ export const ReviewAuth = () => {
{activeAuthEntryIndex + 1}/{authCount} Authorizations
- + ) : ( { className="ReviewAuth__Actions__PublicKey" onClick={() => setIsDropdownOpen(true)} > - +
@@ -241,14 +249,24 @@ const TransferSummary = ({

Receiver

- +

Sender

- +
@@ -293,14 +311,15 @@ const TransferSummary = ({ const AuthDetail = ({ authEntry, + setLoading, + isLoading, }: { authEntry: xdr.SorobanAuthorizationEntry; + setLoading: (isLoading: boolean) => void; + isLoading: boolean; }) => { - // start off in loading state, we always need to fetch token details - const [isLoading, setLoading] = useState(true); const publicKey = useSelector(publicKeySelector); const networkDetails = useSelector(settingsNetworkDetailsSelector); - const sorobanClient = useContext(SorobanContext); const { t } = useTranslation(); const rootInvocation = authEntry.rootInvocation(); @@ -321,68 +340,33 @@ const AuthDetail = ({ const [tokenDetails, setTokenDetails] = React.useState({} as TokenDetailMap); - const tokenDetailsUrl = React.useCallback( - (contractId: string) => - `${INDEXER_URL}/token-details/${contractId}?pub_key=${publicKey}&network=${networkDetails.network}`, - [publicKey, networkDetails.network], - ); - const transfersDepKey = JSON.stringify(transfers); React.useEffect(() => { - async function getTokenDetails() { + async function _getTokenDetails() { setLoading(true); const _tokenDetails = {} as TokenDetailMap; // eslint-disable-next-line for (const transfer of transfers) { try { - if (isCustomNetwork(networkDetails)) { - // eslint-disable-next-line - const tokenBal = await getSorobanTokenBalance( - sorobanClient.server, - transfer.contractId, - { - // eslint-disable-next-line - balance: await sorobanClient.newTxBuilder(), - // eslint-disable-next-line - name: await sorobanClient.newTxBuilder(), - // eslint-disable-next-line - decimals: await sorobanClient.newTxBuilder(), - // eslint-disable-next-line - symbol: await sorobanClient.newTxBuilder(), - }, - [new Address(publicKey).toScVal()], - ); - - if (!tokenBal) { - throw new Error("failed to fetch token details"); - } + // eslint-disable-next-line + const tokenDetailsResponse = await getTokenDetails({ + contractId: transfer.contractId, + publicKey, + networkDetails, + }); + if (!tokenDetailsResponse) { + // default details _tokenDetails[transfer.contractId] = { - name: tokenBal.name, - symbol: tokenBal.symbol, - decimals: tokenBal.decimals, + name: "", + symbol: "", + decimals: 0, }; - setTokenDetails(_tokenDetails); - } else { - // eslint-disable-next-line - const response = await fetch(tokenDetailsUrl(transfer.contractId)); - - if (!response.ok) { - // default details - _tokenDetails[transfer.contractId] = { - name: "", - symbol: "", - decimals: 0, - }; - setTokenDetails(_tokenDetails); - throw new Error("failed to fetch token details"); - } - // eslint-disable-next-line - const _details = await response.json(); - _tokenDetails[transfer.contractId] = _details; + throw new Error("failed to fetch token details"); } + _tokenDetails[transfer.contractId] = tokenDetailsResponse; } catch (error) { captureException( `Failed to fetch token details - ${JSON.stringify(error)}`, @@ -393,9 +377,9 @@ const AuthDetail = ({ setTokenDetails(_tokenDetails); setLoading(false); } - getTokenDetails(); + _getTokenDetails(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [transfersDepKey, tokenDetailsUrl]); + }, [transfersDepKey]); return (
@@ -427,7 +411,12 @@ const AuthDetail = ({
+ } /> + } /> { @@ -29,12 +33,18 @@ export const Security = () => { TODO: Add Change Password Change Password */} - - {t("Show recovery phrase")} + }> + {t("Asset lists")} - + } + > {t("Manage connected apps")} + }> + {t("Show recovery phrase")} + {/* { openTab(newTabHref(ROUTES.accountMigration)); diff --git a/extension/src/popup/views/Settings/index.tsx b/extension/src/popup/views/Settings/index.tsx index 4587d4ed4..46b780a13 100644 --- a/extension/src/popup/views/Settings/index.tsx +++ b/extension/src/popup/views/Settings/index.tsx @@ -1,6 +1,6 @@ import React from "react"; import { useDispatch } from "react-redux"; -import { Button, Heading } from "@stellar/design-system"; +import { Heading, Icon } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; import { ROUTES } from "popup/constants/routes"; @@ -10,6 +10,12 @@ import { ListNavLink, ListNavLinkWrapper } from "popup/basics/ListNavLink"; import { View } from "popup/basics/layout/View"; import { signOut } from "popup/ducks/accountServices"; +import IconNetwork from "popup/assets/icon-settings-network.svg?react"; +import IconSecurity from "popup/assets/icon-settings-security.svg?react"; +import IconHelp from "popup/assets/icon-settings-help.svg?react"; +import IconFeedback from "popup/assets/icon-settings-feedback.svg?react"; +import IconAbout from "popup/assets/icon-settings-about.svg?react"; +import IconLogout from "popup/assets/icon-settings-logout.svg?react"; import packageJson from "../../../../package.json"; @@ -28,19 +34,7 @@ export const Settings = () => { return ( <> - - -
- } - > + diff --git a/extension/src/popup/views/Settings/styles.scss b/extension/src/popup/views/Settings/styles.scss index 2aec8c811..b6186faf7 100644 --- a/extension/src/popup/views/Settings/styles.scss +++ b/extension/src/popup/views/Settings/styles.scss @@ -11,8 +11,25 @@ font-size: 0.875rem; } - &__logout { + &__row { + display: flex; + align-items: center; + gap: 0.75rem; + } + + &__icon { display: flex; - justify-content: center; + align-items: center; + width: 1.25rem; + height: 1.25rem; + + &__preferences { + fill: var(--color-gray-60); + } + } + + &__logout { + color: var(--color-red-50); + cursor: pointer; } } diff --git a/extension/src/popup/views/SignTransaction/index.tsx b/extension/src/popup/views/SignTransaction/index.tsx index e83063791..0ad5c2309 100644 --- a/extension/src/popup/views/SignTransaction/index.tsx +++ b/extension/src/popup/views/SignTransaction/index.tsx @@ -316,7 +316,10 @@ export const SignTransaction = () => { className="SignTransaction__Actions__PublicKey" onClick={() => setIsDropdownOpen(true)} > - +
diff --git a/extension/src/popup/views/Swap/index.tsx b/extension/src/popup/views/Swap/index.tsx index dd526ca43..5b88dae73 100644 --- a/extension/src/popup/views/Swap/index.tsx +++ b/extension/src/popup/views/Swap/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect } from "react"; +import React, { useEffect } from "react"; import { useSelector, useDispatch } from "react-redux"; import { Switch, Redirect } from "react-router-dom"; @@ -18,16 +18,12 @@ import { import { publicKeySelector } from "popup/ducks/accountServices"; import { settingsNetworkDetailsSelector } from "popup/ducks/settings"; -import { SorobanContext } from "popup/SorobanContext"; - export const Swap = () => { const dispatch: AppDispatch = useDispatch(); const { accountBalances } = useSelector(transactionSubmissionSelector); const publicKey = useSelector(publicKeySelector); const networkDetails = useSelector(settingsNetworkDetailsSelector); - const sorobanClient = useContext(SorobanContext); - // load needed swap data here in case didn't go to home screen first useEffect(() => { (async () => { @@ -36,7 +32,6 @@ export const Swap = () => { getAccountBalances({ publicKey, networkDetails, - sorobanClient, }), ); @@ -50,7 +45,7 @@ export const Swap = () => { } } })(); - }, [dispatch, publicKey, networkDetails, accountBalances, sorobanClient]); + }, [dispatch, publicKey, networkDetails, accountBalances]); return ( diff --git a/extension/src/popup/views/ViewPublicKey/index.tsx b/extension/src/popup/views/ViewPublicKey/index.tsx index 45ccc1172..965fda839 100644 --- a/extension/src/popup/views/ViewPublicKey/index.tsx +++ b/extension/src/popup/views/ViewPublicKey/index.tsx @@ -6,10 +6,11 @@ import { object as YupObject, string as YupString } from "yup"; import { Icon, Input, CopyText, Button } from "@stellar/design-system"; import { useTranslation } from "react-i18next"; -import { PillButton } from "popup/basics/buttons/PillButton"; +import { isCustomNetwork } from "@shared/helpers/stellar"; +import { PillButton } from "popup/basics/buttons/PillButton"; import { emitMetric } from "helpers/metrics"; -import { truncatedPublicKey, isCustomNetwork } from "helpers/stellar"; +import { truncatedPublicKey } from "helpers/stellar"; import { METRIC_NAMES } from "popup/constants/metricsNames"; import { openTab } from "popup/helpers/navigate"; diff --git a/extension/src/popup/views/__tests__/ManageAssets.test.tsx b/extension/src/popup/views/__tests__/ManageAssets.test.tsx index abc64921c..b40d4be8b 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/extension/src/popup/views/__tests__/SignTransaction.test.tsx b/extension/src/popup/views/__tests__/SignTransaction.test.tsx index 22805b989..4c40187c7 100644 --- a/extension/src/popup/views/__tests__/SignTransaction.test.tsx +++ b/extension/src/popup/views/__tests__/SignTransaction.test.tsx @@ -218,12 +218,15 @@ describe("SignTransactions", () => { expect( opDetails.includes( - `Parameters${args?.from.toString()}${args?.to.toString()}${args?.amount.toString()}`, + `Parameters${args?.from.toString()}Copied${args?.to.toString()}Copied${args?.amount.toString()}`, ), ).toBeTruthy(); expect( opDetails.includes( - `Contract ID${Stellar.truncatedPublicKey(args?.contractId || "")}`, + `Contract ID${Stellar.truncatedPublicKey( + args?.contractId || "", + 6, + )}Copied`, ), ).toBeTruthy(); expect(opDetails.includes(`Function Name${args?.fnName}`)).toBeTruthy(); @@ -273,12 +276,15 @@ describe("SignTransactions", () => { expect( opDetails.includes( - `Parameters${args?.to.toString()}${args?.amount.toString()}`, + `Parameters${args?.to.toString()}Copied${args?.amount.toString()}`, ), ).toBeTruthy(); expect( opDetails.includes( - `Contract ID${Stellar.truncatedPublicKey(args?.contractId || "")}`, + `Contract ID${Stellar.truncatedPublicKey( + args?.contractId || "", + 6, + )}Copied`, ), ).toBeTruthy(); expect(opDetails.includes(`Function Name${args?.fnName}`)).toBeTruthy(); diff --git a/extension/webpack.common.js b/extension/webpack.common.js index 5432334d3..234c8021a 100644 --- a/extension/webpack.common.js +++ b/extension/webpack.common.js @@ -82,8 +82,15 @@ const commonConfig = ( type: "asset/resource", }, { - test: /\.svg$/, - type: "asset/resource", + test: /\.svg$/i, + type: "asset", + resourceQuery: { not: [/react/] }, // exclude react component if *.svg?react + }, + { + test: /\.svg$/i, + issuer: /\.[jt]sx?$/, + resourceQuery: /react/, // *.svg?react + use: ["@svgr/webpack"], }, { test: /\.(css|sass|scss)$/, diff --git a/jest.config.js b/jest.config.js index f2ab1bacf..cd4f4c235 100644 --- a/jest.config.js +++ b/jest.config.js @@ -22,6 +22,7 @@ const jsdomTests = { moduleNameMapper: { "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/config/jest/__mocks__/fileMock.ts", + "^.+\\.svg\\?(react)(.+)?$": "/config/jest/__mocks__/fileMock.ts", "\\.(scss|css)$": "/config/jest/__mocks__/styleMock.ts", }, moduleFileExtensions: ["js", "jsx", "json", "node", "mjs", "ts", "tsx"], diff --git a/yarn.lock b/yarn.lock index 4ec3df4b1..89cc5e997 100644 --- a/yarn.lock +++ b/yarn.lock @@ -304,6 +304,14 @@ "@babel/highlight" "^7.23.4" chalk "^2.4.2" +"@babel/code-frame@^7.24.1", "@babel/code-frame@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + "@babel/compat-data@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.1.tgz" @@ -329,6 +337,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== +"@babel/compat-data@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" + integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== + "@babel/core@7.12.9": version "7.12.9" resolved "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz" @@ -457,6 +470,27 @@ json5 "^2.2.2" semver "^6.3.0" +"@babel/core@^7.21.3": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.4.tgz#1f758428e88e0d8c563874741bc4ffc4f71a4717" + integrity sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.4" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helpers" "^7.24.4" + "@babel/parser" "^7.24.4" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/eslint-parser@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.12.1.tgz" @@ -532,6 +566,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.24.1", "@babel/generator@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.4.tgz#1fc55532b88adf952025d5d2d1e71f946cb1c498" + integrity sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw== + dependencies: + "@babel/types" "^7.24.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz" @@ -685,6 +729,21 @@ "@babel/helper-split-export-declaration" "^7.22.6" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.4.tgz#c806f73788a6800a5cfbbc04d2df7ee4d927cce3" + integrity sha512-lG75yeuUSVu0pIcbhiYMXBXANHrpUPaOfu7ryAzskCgKUHuAxRQI5ssrtmF0X9UXldPlvT0XM/A4F44OXRt6iQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.1.tgz" @@ -743,6 +802,17 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" +"@babel/helper-define-polyfill-provider@^0.6.1": + version "0.6.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.1.tgz#fadc63f0c2ff3c8d02ed905dcea747c5b0fb74fd" + integrity sha512-o7SDgTJuvx5vLKD6SFvkydkSMBvahDKGiNJzG22IZYXhiqoe9efY7zocICBgzHV4IRg5wdgl2nEL/tulKIEIbA== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + "@babel/helper-environment-visitor@^7.18.9": version "7.18.9" resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" @@ -884,6 +954,13 @@ dependencies: "@babel/types" "^7.22.15" +"@babel/helper-module-imports@^7.24.1": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" + integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== + dependencies: + "@babel/types" "^7.24.0" + "@babel/helper-module-transforms@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz" @@ -1096,6 +1173,15 @@ "@babel/helper-member-expression-to-functions" "^7.22.15" "@babel/helper-optimise-call-expression" "^7.22.5" +"@babel/helper-replace-supers@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz#7085bd19d4a0b7ed8f405c1ed73ccb70f323abc1" + integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-simple-access@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz" @@ -1314,6 +1400,15 @@ "@babel/traverse" "^7.24.0" "@babel/types" "^7.24.0" +"@babel/helpers@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.4.tgz#dc00907fd0d95da74563c142ef4cd21f2cb856b6" + integrity sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.1" + "@babel/types" "^7.24.0" + "@babel/highlight@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz" @@ -1359,6 +1454,16 @@ chalk "^2.4.2" js-tokens "^4.0.0" +"@babel/highlight@^7.24.2": + version "7.24.2" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.2.tgz#3f539503efc83d3c59080a10e6634306e0370d26" + integrity sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.7.0": version "7.12.3" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz" @@ -1394,6 +1499,19 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.0.tgz#26a3d1ff49031c53a97d03b604375f028746a9ac" integrity sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg== +"@babel/parser@^7.24.1", "@babel/parser@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.4.tgz#234487a110d89ad5a3ed4a8a566c36b9453e8c88" + integrity sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg== + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.4.tgz#6125f0158543fb4edf1c22f322f3db67f21cb3e1" + integrity sha512-qpl6vOOEEzTLLcsuqYYo8yDtrTocmu2xkGvgNebvPjT9DTtfFYGmgDqY+rBYXNlqL4s9qLDn6xkrJv4RxAPiTA== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz" @@ -1408,6 +1526,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz#b645d9ba8c2bc5b7af50f0fe949f9edbeb07c8cf" + integrity sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": version "7.20.7" resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz" @@ -1426,6 +1551,15 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-transform-optional-chaining" "^7.23.3" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz#da8261f2697f0f41b0855b91d3a20a1fbfd271d3" + integrity sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.24.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.23.7": version "7.23.7" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz#516462a95d10a9618f197d39ad291a9b47ae1d7b" @@ -1434,6 +1568,14 @@ "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz#1181d9685984c91d657b8ddf14f0487a6bab2988" + integrity sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-proposal-async-generator-functions@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz" @@ -1770,6 +1912,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-import-assertions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz#db3aad724153a00eaac115a3fb898de544e34971" + integrity sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-import-attributes@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz#992aee922cf04512461d7dae3ff6951b90a2dc06" @@ -1777,6 +1926,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-import-attributes@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz#c66b966c63b714c4eec508fcf5763b1f2d381093" + integrity sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" @@ -1812,6 +1968,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-jsx@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" + integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" @@ -1896,6 +2059,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-typescript@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.1.tgz#b3bcc51f396d15f3591683f90239de143c076844" + integrity sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-typescript@^7.7.2": version "7.18.6" resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz" @@ -1932,6 +2102,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-arrow-functions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz#2bf263617060c9cc45bcdbf492b8cc805082bf27" + integrity sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-async-generator-functions@^7.23.9": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.9.tgz#9adaeb66fc9634a586c5df139c6240d41ed801ce" @@ -1942,6 +2119,16 @@ "@babel/helper-remap-async-to-generator" "^7.22.20" "@babel/plugin-syntax-async-generators" "^7.8.4" +"@babel/plugin-transform-async-generator-functions@^7.24.3": + version "7.24.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz#8fa7ae481b100768cc9842c8617808c5352b8b89" + integrity sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-transform-async-to-generator@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz" @@ -1969,6 +2156,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-remap-async-to-generator" "^7.22.20" +"@babel/plugin-transform-async-to-generator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz#0e220703b89f2216800ce7b1c53cb0cf521c37f4" + integrity sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw== + dependencies: + "@babel/helper-module-imports" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/plugin-transform-block-scoped-functions@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz" @@ -1990,6 +2186,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-block-scoped-functions@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz#1c94799e20fcd5c4d4589523bbc57b7692979380" + integrity sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-block-scoping@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz" @@ -2011,6 +2214,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-block-scoping@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.4.tgz#28f5c010b66fbb8ccdeef853bef1935c434d7012" + integrity sha512-nIFUZIpGKDf9O9ttyRXpHFpKC+X3Y5mtshZONuEUYBomAKoM4y029Jr+uB1bHGPhNmK8YXHevDtKDOLmtRrp6g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-class-properties@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz#35c377db11ca92a785a718b6aa4e3ed1eb65dc48" @@ -2019,6 +2229,14 @@ "@babel/helper-create-class-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-class-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz#bcbf1aef6ba6085cfddec9fc8d58871cf011fc29" + integrity sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-class-static-block@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz#2a202c8787a8964dd11dfcedf994d36bfc844ab5" @@ -2028,6 +2246,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" +"@babel/plugin-transform-class-static-block@^7.24.4": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz#1a4653c0cf8ac46441ec406dece6e9bc590356a4" + integrity sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.4" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-transform-classes@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz" @@ -2071,6 +2298,20 @@ "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" +"@babel/plugin-transform-classes@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.1.tgz#5bc8fc160ed96378184bc10042af47f50884dcb1" + integrity sha512-ZTIe3W7UejJd3/3R4p7ScyyOoafetUShSf4kCqV0O7F/RiHxVj/wRaRnQlrGwflvcehNA8M42HkAiEDYZu2F1Q== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-split-export-declaration" "^7.22.6" + globals "^11.1.0" + "@babel/plugin-transform-computed-properties@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz" @@ -2094,6 +2335,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/template" "^7.22.15" +"@babel/plugin-transform-computed-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz#bc7e787f8e021eccfb677af5f13c29a9934ed8a7" + integrity sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/template" "^7.24.0" + "@babel/plugin-transform-destructuring@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz" @@ -2115,6 +2364,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-destructuring@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.1.tgz#b1e8243af4a0206841973786292b8c8dd8447345" + integrity sha512-ow8jciWqNxR3RYbSNVuF4U2Jx130nwnBnhRw6N6h1bOejNkABmcI5X5oz29K4alWX7vf1C+o6gtKXikzRKkVdw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-dotall-regex@^7.12.1", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz" @@ -2139,6 +2395,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-dotall-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz#d56913d2f12795cc9930801b84c6f8c47513ac13" + integrity sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-duplicate-keys@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz" @@ -2160,6 +2424,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-duplicate-keys@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz#5347a797fe82b8d09749d10e9f5b83665adbca88" + integrity sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-dynamic-import@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz#c7629e7254011ac3630d47d7f34ddd40ca535143" @@ -2168,6 +2439,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" +"@babel/plugin-transform-dynamic-import@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz#2a5a49959201970dd09a5fca856cb651e44439dd" + integrity sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-exponentiation-operator@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz" @@ -2192,6 +2471,14 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-exponentiation-operator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz#6650ebeb5bd5c012d5f5f90a26613a08162e8ba4" + integrity sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-export-namespace-from@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz#084c7b25e9a5c8271e987a08cf85807b80283191" @@ -2200,6 +2487,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" +"@babel/plugin-transform-export-namespace-from@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz#f033541fc036e3efb2dcb58eedafd4f6b8078acd" + integrity sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-transform-flow-strip-types@^7.16.0": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.23.3.tgz#cfa7ca159cc3306fab526fc67091556b51af26ff" @@ -2230,6 +2525,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" +"@babel/plugin-transform-for-of@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz#67448446b67ab6c091360ce3717e7d3a59e202fd" + integrity sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-function-name@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz" @@ -2256,6 +2559,15 @@ "@babel/helper-function-name" "^7.23.0" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-function-name@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz#8cba6f7730626cc4dfe4ca2fa516215a0592b361" + integrity sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA== + dependencies: + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-json-strings@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz#a871d9b6bd171976efad2e43e694c961ffa3714d" @@ -2264,6 +2576,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-json-strings" "^7.8.3" +"@babel/plugin-transform-json-strings@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz#08e6369b62ab3e8a7b61089151b161180c8299f7" + integrity sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-transform-literals@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz" @@ -2285,6 +2605,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz#0a1982297af83e6b3c94972686067df588c5c096" + integrity sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-logical-assignment-operators@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz#e599f82c51d55fac725f62ce55d3a0886279ecb5" @@ -2293,6 +2620,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" +"@babel/plugin-transform-logical-assignment-operators@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz#719d8aded1aa94b8fb34e3a785ae8518e24cfa40" + integrity sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-transform-member-expression-literals@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz" @@ -2314,6 +2649,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-member-expression-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz#896d23601c92f437af8b01371ad34beb75df4489" + integrity sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-modules-amd@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz" @@ -2339,6 +2681,14 @@ "@babel/helper-module-transforms" "^7.23.3" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-modules-amd@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz#b6d829ed15258536977e9c7cc6437814871ffa39" + integrity sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-modules-commonjs@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz" @@ -2367,6 +2717,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" +"@babel/plugin-transform-modules-commonjs@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9" + integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-simple-access" "^7.22.5" + "@babel/plugin-transform-modules-systemjs@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz" @@ -2398,6 +2757,16 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.20" +"@babel/plugin-transform-modules-systemjs@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz#2b9625a3d4e445babac9788daec39094e6b11e3e" + integrity sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA== + dependencies: + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-identifier" "^7.22.20" + "@babel/plugin-transform-modules-umd@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz" @@ -2422,6 +2791,14 @@ "@babel/helper-module-transforms" "^7.23.3" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-modules-umd@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz#69220c66653a19cf2c0872b9c762b9a48b8bebef" + integrity sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-named-capturing-groups-regex@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz" @@ -2466,6 +2843,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-new-target@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz#29c59988fa3d0157de1c871a28cd83096363cc34" + integrity sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-nullish-coalescing-operator@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz#45556aad123fc6e52189ea749e33ce090637346e" @@ -2474,6 +2858,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz#0cd494bb97cb07d428bd651632cb9d4140513988" + integrity sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-transform-numeric-separator@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz#03d08e3691e405804ecdd19dd278a40cca531f29" @@ -2482,6 +2874,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" +"@babel/plugin-transform-numeric-separator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz#5bc019ce5b3435c1cadf37215e55e433d674d4e8" + integrity sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-transform-object-rest-spread@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.0.tgz#7b836ad0088fdded2420ce96d4e1d3ed78b71df1" @@ -2493,6 +2893,16 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.23.3" +"@babel/plugin-transform-object-rest-spread@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz#5a3ce73caf0e7871a02e1c31e8b473093af241ff" + integrity sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA== + dependencies: + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.24.1" + "@babel/plugin-transform-object-super@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz" @@ -2517,6 +2927,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-replace-supers" "^7.22.20" +"@babel/plugin-transform-object-super@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz#e71d6ab13483cca89ed95a474f542bbfc20a0520" + integrity sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/plugin-transform-optional-catch-binding@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz#318066de6dacce7d92fa244ae475aa8d91778017" @@ -2525,6 +2943,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" +"@babel/plugin-transform-optional-catch-binding@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz#92a3d0efe847ba722f1a4508669b23134669e2da" + integrity sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-transform-optional-chaining@^7.23.3", "@babel/plugin-transform-optional-chaining@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz#6acf61203bdfc4de9d4e52e64490aeb3e52bd017" @@ -2534,6 +2960,15 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-syntax-optional-chaining" "^7.8.3" +"@babel/plugin-transform-optional-chaining@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.1.tgz#26e588acbedce1ab3519ac40cc748e380c5291e6" + integrity sha512-n03wmDt+987qXwAgcBlnUUivrZBPZ8z1plL0YvgQalLm+ZE5BMhGm94jhxXtA1wzv1Cu2aaOv1BM9vbVttrzSg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-transform-parameters@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz" @@ -2555,6 +2990,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-parameters@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz#983c15d114da190506c75b616ceb0f817afcc510" + integrity sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-private-methods@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz#b2d7a3c97e278bfe59137a978d53b2c2e038c0e4" @@ -2563,6 +3005,14 @@ "@babel/helper-create-class-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-private-methods@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz#a0faa1ae87eff077e1e47a5ec81c3aef383dc15a" + integrity sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-private-property-in-object@^7.23.4": version "7.23.4" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz#3ec711d05d6608fd173d9b8de39872d8dbf68bf5" @@ -2573,6 +3023,16 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" +"@babel/plugin-transform-private-property-in-object@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.1.tgz#756443d400274f8fb7896742962cc1b9f25c1f6a" + integrity sha512-pTHxDVa0BpUbvAgX3Gat+7cSciXqUcY9j2VZKTbSB6+VQGpNgNO9ailxTGHSXlqOnX1Hcx1Enme2+yv7VqP9bg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-transform-property-literals@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz" @@ -2594,6 +3054,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-property-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz#d6a9aeab96f03749f4eebeb0b6ea8e90ec958825" + integrity sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-react-constant-elements@^7.18.12": version "7.20.2" resolved "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.20.2.tgz" @@ -2601,6 +3068,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.20.2" +"@babel/plugin-transform-react-constant-elements@^7.21.3": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.24.1.tgz#d493a0918b9fdad7540f5afd9b5eb5c52500d18d" + integrity sha512-QXp1U9x0R7tkiGB0FOk8o74jhnap0FlZ5gNkRIWdG3eP+SvMFg118e1zaWewDzgABb106QSKpVsD3Wgd8t6ifA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-react-display-name@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.12.1.tgz" @@ -2738,6 +3212,14 @@ "@babel/helper-plugin-utils" "^7.22.5" regenerator-transform "^0.15.2" +"@babel/plugin-transform-regenerator@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz#625b7545bae52363bdc1fbbdc7252b5046409c8c" + integrity sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + regenerator-transform "^0.15.2" + "@babel/plugin-transform-reserved-words@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz" @@ -2759,6 +3241,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-reserved-words@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz#8de729f5ecbaaf5cf83b67de13bad38a21be57c1" + integrity sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-runtime@^7.16.4": version "7.24.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.0.tgz#e308fe27d08b74027d42547081eefaf4f2ffbcc9" @@ -2804,6 +3293,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-shorthand-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz#ba9a09144cf55d35ec6b93a32253becad8ee5b55" + integrity sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-spread@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz" @@ -2828,6 +3324,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" +"@babel/plugin-transform-spread@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz#a1acf9152cbf690e4da0ba10790b3ac7d2b2b391" + integrity sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-sticky-regex@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.1.tgz" @@ -2850,6 +3354,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-sticky-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz#f03e672912c6e203ed8d6e0271d9c2113dc031b9" + integrity sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-template-literals@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz" @@ -2871,6 +3382,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-template-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz#15e2166873a30d8617e3e2ccadb86643d327aab7" + integrity sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-typeof-symbol@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz" @@ -2892,6 +3410,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-typeof-symbol@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.1.tgz#6831f78647080dec044f7e9f68003d99424f94c7" + integrity sha512-CBfU4l/A+KruSUoW+vTQthwcAdwuqbpRNB8HQKlZABwHRhsdHZ9fezp4Sn18PeAlYxTNiLMlx4xUBV3AWfg1BA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-typescript@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.1.tgz" @@ -2920,6 +3445,16 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.23.3" +"@babel/plugin-transform-typescript@^7.24.1": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.4.tgz#03e0492537a4b953e491f53f2bc88245574ebd15" + integrity sha512-79t3CQ8+oBGk/80SQ8MN3Bs3obf83zJ0YZjDmDaEZN8MqhMI760apl5z6a20kFeMXBwJX99VpKT8CKxEBp5H1g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.4" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-typescript" "^7.24.1" + "@babel/plugin-transform-unicode-escapes@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz" @@ -2941,6 +3476,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-escapes@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz#fb3fa16676549ac7c7449db9b342614985c2a3a4" + integrity sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-unicode-property-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz#19e234129e5ffa7205010feec0d94c251083d7ad" @@ -2949,6 +3491,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-property-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz#56704fd4d99da81e5e9f0c0c93cabd91dbc4889e" + integrity sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-unicode-regex@^7.12.1": version "7.12.1" resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz" @@ -2973,6 +3523,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz#57c3c191d68f998ac46b708380c1ce4d13536385" + integrity sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-transform-unicode-sets-regex@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz#4fb6f0a719c2c5859d11f6b55a050cc987f3799e" @@ -2981,6 +3539,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.15" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-sets-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz#c1ea175b02afcffc9cf57a9c4658326625165b7f" + integrity sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/preset-env@^7.11.0": version "7.12.1" resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.1.tgz" @@ -3220,6 +3786,93 @@ core-js-compat "^3.25.1" semver "^6.3.0" +"@babel/preset-env@^7.20.2": + version "7.24.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.4.tgz#46dbbcd608771373b88f956ffb67d471dce0d23b" + integrity sha512-7Kl6cSmYkak0FK/FXjSEnLJ1N9T/WA2RkMhu17gZ/dsxKJUuTYNIylahPTzqpLyJN4WhDif8X0XK1R8Wsguo/A== + dependencies: + "@babel/compat-data" "^7.24.4" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.4" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.1" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.24.1" + "@babel/plugin-syntax-import-attributes" "^7.24.1" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.24.1" + "@babel/plugin-transform-async-generator-functions" "^7.24.3" + "@babel/plugin-transform-async-to-generator" "^7.24.1" + "@babel/plugin-transform-block-scoped-functions" "^7.24.1" + "@babel/plugin-transform-block-scoping" "^7.24.4" + "@babel/plugin-transform-class-properties" "^7.24.1" + "@babel/plugin-transform-class-static-block" "^7.24.4" + "@babel/plugin-transform-classes" "^7.24.1" + "@babel/plugin-transform-computed-properties" "^7.24.1" + "@babel/plugin-transform-destructuring" "^7.24.1" + "@babel/plugin-transform-dotall-regex" "^7.24.1" + "@babel/plugin-transform-duplicate-keys" "^7.24.1" + "@babel/plugin-transform-dynamic-import" "^7.24.1" + "@babel/plugin-transform-exponentiation-operator" "^7.24.1" + "@babel/plugin-transform-export-namespace-from" "^7.24.1" + "@babel/plugin-transform-for-of" "^7.24.1" + "@babel/plugin-transform-function-name" "^7.24.1" + "@babel/plugin-transform-json-strings" "^7.24.1" + "@babel/plugin-transform-literals" "^7.24.1" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.1" + "@babel/plugin-transform-member-expression-literals" "^7.24.1" + "@babel/plugin-transform-modules-amd" "^7.24.1" + "@babel/plugin-transform-modules-commonjs" "^7.24.1" + "@babel/plugin-transform-modules-systemjs" "^7.24.1" + "@babel/plugin-transform-modules-umd" "^7.24.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.24.1" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.1" + "@babel/plugin-transform-numeric-separator" "^7.24.1" + "@babel/plugin-transform-object-rest-spread" "^7.24.1" + "@babel/plugin-transform-object-super" "^7.24.1" + "@babel/plugin-transform-optional-catch-binding" "^7.24.1" + "@babel/plugin-transform-optional-chaining" "^7.24.1" + "@babel/plugin-transform-parameters" "^7.24.1" + "@babel/plugin-transform-private-methods" "^7.24.1" + "@babel/plugin-transform-private-property-in-object" "^7.24.1" + "@babel/plugin-transform-property-literals" "^7.24.1" + "@babel/plugin-transform-regenerator" "^7.24.1" + "@babel/plugin-transform-reserved-words" "^7.24.1" + "@babel/plugin-transform-shorthand-properties" "^7.24.1" + "@babel/plugin-transform-spread" "^7.24.1" + "@babel/plugin-transform-sticky-regex" "^7.24.1" + "@babel/plugin-transform-template-literals" "^7.24.1" + "@babel/plugin-transform-typeof-symbol" "^7.24.1" + "@babel/plugin-transform-unicode-escapes" "^7.24.1" + "@babel/plugin-transform-unicode-property-regex" "^7.24.1" + "@babel/plugin-transform-unicode-regex" "^7.24.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.24.1" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.4" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.31.0" + semver "^6.3.1" + "@babel/preset-modules@0.1.6-no-external-plugins": version "0.1.6-no-external-plugins" resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" @@ -3316,6 +3969,17 @@ "@babel/helper-validator-option" "^7.18.6" "@babel/plugin-transform-typescript" "^7.18.6" +"@babel/preset-typescript@^7.21.0": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.24.1.tgz#89bdf13a3149a17b3b2a2c9c62547f06db8845ec" + integrity sha512-1DBaMmRDpuYQBPWD8Pf/WEwCrtgRHxsZnP4mIy9G/X+hFfbI47Q2G4t1Paakld84+qsk2fSsUPMKg71jkoOOaQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-syntax-jsx" "^7.24.1" + "@babel/plugin-transform-modules-commonjs" "^7.24.1" + "@babel/plugin-transform-typescript" "^7.24.1" + "@babel/regjsgen@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" @@ -3472,6 +4136,22 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.24.1": + version "7.24.1" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.1.tgz#d65c36ac9dd17282175d1e4a3c49d5b7988f530c" + integrity sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ== + dependencies: + "@babel/code-frame" "^7.24.1" + "@babel/generator" "^7.24.1" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.24.1" + "@babel/types" "^7.24.0" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.12.1", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.12.1" resolved "https://registry.npmjs.org/@babel/types/-/types-7.12.1.tgz" @@ -4371,6 +5051,15 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/resolve-uri@3.1.0", "@jridgewell/resolve-uri@^3.0.3": version "3.1.0" resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" @@ -4386,6 +5075,11 @@ resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + "@jridgewell/source-map@^0.3.2": version "0.3.2" resolved "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz" @@ -4436,6 +5130,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@lavamoat/aa@^3.1.1": version "3.1.2" resolved "https://registry.yarnpkg.com/@lavamoat/aa/-/aa-3.1.2.tgz#3e2c0bbff791204bb4dabe96c2486b0c910e1897" @@ -4831,11 +5533,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" @@ -4906,6 +5603,11 @@ tweetnacl "^1.0.1" tweetnacl-util "^0.15.0" +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + "@svgr/babel-plugin-add-jsx-attribute@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz" @@ -4916,36 +5618,85 @@ resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-6.5.0.tgz" integrity sha512-8zYdkym7qNyfXpWvu4yq46k41pyNM9SOstoWhKlm+IfdCE1DdnRKeMUPsWIEO/DEkaWxJ8T9esNdG3QwQ93jBA== +"@svgr/babel-plugin-remove-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + "@svgr/babel-plugin-remove-jsx-empty-expression@*": version "6.5.0" resolved "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-6.5.0.tgz" integrity sha512-NFdxMq3xA42Kb1UbzCVxplUc0iqSyM9X8kopImvFnB+uSDdzIHOdbs1op8ofAvVRtbg4oZiyRl3fTYeKcOe9Iw== +"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + "@svgr/babel-plugin-replace-jsx-attribute-value@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.5.1.tgz" integrity sha512-8DPaVVE3fd5JKuIC29dqyMB54sA6mfgki2H2+swh+zNJoynC8pMPzOkidqHOSc6Wj032fhl8Z0TVn1GiPpAiJg== +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== + "@svgr/babel-plugin-svg-dynamic-title@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.5.1.tgz" integrity sha512-FwOEi0Il72iAzlkaHrlemVurgSQRDFbk0OC8dSvD5fSBPHltNh7JtLsxmZUhjYBZo2PpcU/RJvvi6Q0l7O7ogw== +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== + "@svgr/babel-plugin-svg-em-dimensions@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.5.1.tgz" integrity sha512-gWGsiwjb4tw+ITOJ86ndY/DZZ6cuXMNE/SjcDRg+HLuCmwpcjOktwRF9WgAiycTqJD/QXqL2f8IzE2Rzh7aVXA== +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== + "@svgr/babel-plugin-transform-react-native-svg@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.5.1.tgz" integrity sha512-2jT3nTayyYP7kI6aGutkyfJ7UMGtuguD72OjeGLwVNyfPRBD8zQthlvL+fAbAKk5n9ZNcvFkp/b1lZ7VsYqVJg== +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== + "@svgr/babel-plugin-transform-svg-component@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.5.1.tgz" integrity sha512-a1p6LF5Jt33O3rZoVRBqdxL350oge54iZWHNI6LJB5tQ7EelvD/Mb1mfBiZNAan0dt4i3VArkFRjA4iObuNykQ== +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" + "@svgr/babel-preset@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-6.5.1.tgz" @@ -4960,6 +5711,17 @@ "@svgr/babel-plugin-transform-react-native-svg" "^6.5.1" "@svgr/babel-plugin-transform-svg-component" "^6.5.1" +"@svgr/core@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" + "@svgr/core@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/core/-/core-6.5.1.tgz" @@ -4971,6 +5733,14 @@ camelcase "^6.2.0" cosmiconfig "^7.0.1" +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== + dependencies: + "@babel/types" "^7.21.3" + entities "^4.4.0" + "@svgr/hast-util-to-babel-ast@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.5.1.tgz" @@ -4979,6 +5749,16 @@ "@babel/types" "^7.20.0" entities "^4.4.0" +"@svgr/plugin-jsx@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" + "@svgr/plugin-jsx@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-6.5.1.tgz" @@ -4989,6 +5769,15 @@ "@svgr/hast-util-to-babel-ast" "^6.5.1" svg-parser "^2.0.4" +"@svgr/plugin-svgo@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz#b115b7b967b564f89ac58feae89b88c3decd0f00" + integrity sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA== + dependencies: + cosmiconfig "^8.1.3" + deepmerge "^4.3.1" + svgo "^3.0.2" + "@svgr/plugin-svgo@^6.5.1": version "6.5.1" resolved "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-6.5.1.tgz" @@ -5012,6 +5801,20 @@ "@svgr/plugin-jsx" "^6.5.1" "@svgr/plugin-svgo" "^6.5.1" +"@svgr/webpack@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-8.1.0.tgz#16f1b5346f102f89fda6ec7338b96a701d8be0c2" + integrity sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA== + dependencies: + "@babel/core" "^7.21.3" + "@babel/plugin-transform-react-constant-elements" "^7.21.3" + "@babel/preset-env" "^7.20.2" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.21.0" + "@svgr/core" "8.1.0" + "@svgr/plugin-jsx" "8.1.0" + "@svgr/plugin-svgo" "8.1.0" + "@szmarczak/http-timer@^1.1.2": version "1.1.2" resolved "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz" @@ -5488,13 +6291,20 @@ dependencies: "@types/react" "*" -"@types/react-dom@^18.0.0", "@types/react-dom@^18.2.20": +"@types/react-dom@^18.0.0": version "18.2.20" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.20.tgz#cbdf7abb3cc2377980bb1294bc51375016a8320f" integrity sha512-HXN/biJY8nv20Cn9ZbCFq3liERd4CozVZmKbaiZ9KiKTrWqsP7eoGDO6OOGvJQwoVFuiXaiJ7nBBjiFFbRmQMQ== dependencies: "@types/react" "*" +"@types/react-dom@^18.2.24": + version "18.2.24" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.24.tgz#8dda8f449ae436a7a6e91efed8035d4ab03ff759" + integrity sha512-cN6upcKd8zkGy4HU9F1+/s98Hrp6D4MOcippK4PoE8OZRngohHZpbJn1GsaDLz87MqvHNoT13nHvNqM9ocRHZg== + dependencies: + "@types/react" "*" + "@types/react-native@*": version "0.63.30" resolved "https://registry.npmjs.org/@types/react-native/-/react-native-0.63.30.tgz" @@ -6846,6 +7656,15 @@ babel-plugin-polyfill-corejs2@^0.3.3: "@babel/helper-define-polyfill-provider" "^0.3.3" semver "^6.1.1" +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.10" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.10.tgz#276f41710b03a64f6467433cab72cbc2653c38b1" + integrity sha512-rpIuu//y5OX6jVU+a5BCn1R5RSZYWAl2Nar76iwaOdycqb6JPxediskWFMMl7stfwNJR4b7eiQvh5fB5TEQJTQ== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.1" + semver "^6.3.1" + babel-plugin-polyfill-corejs2@^0.4.8: version "0.4.8" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.8.tgz#dbcc3c8ca758a290d47c3c6a490d59429b0d2269" @@ -6855,6 +7674,14 @@ babel-plugin-polyfill-corejs2@^0.4.8: "@babel/helper-define-polyfill-provider" "^0.5.0" semver "^6.3.1" +babel-plugin-polyfill-corejs3@^0.10.4: + version "0.10.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" + integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.1" + core-js-compat "^3.36.1" + babel-plugin-polyfill-corejs3@^0.6.0: version "0.6.0" resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz" @@ -6885,6 +7712,13 @@ babel-plugin-polyfill-regenerator@^0.5.5: dependencies: "@babel/helper-define-polyfill-provider" "^0.5.0" +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.1.tgz#4f08ef4c62c7a7f66a35ed4c0d75e30506acc6be" + integrity sha512-JfTApdE++cgcTWjsiCQlLyFBMbTUft9ja17saCc93lgV33h4tuCVj7tlvu//qpLwaG+3yEz7/KhahGrUMkVq9g== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.1" + "babel-plugin-styled-components@>= 1": version "1.11.1" resolved "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.11.1.tgz" @@ -7224,7 +8058,7 @@ browserslist@^4.20.2: node-releases "^2.0.6" update-browserslist-db "^1.0.5" -browserslist@^4.22.2, browserslist@^4.22.3: +browserslist@^4.22.2, browserslist@^4.22.3, browserslist@^4.23.0: version "4.23.0" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== @@ -8102,6 +8936,13 @@ core-js-compat@^3.31.0, core-js-compat@^3.34.0: dependencies: browserslist "^4.22.3" +core-js-compat@^3.36.1: + version "3.36.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.36.1.tgz#1818695d72c99c25d621dca94e6883e190cea3c8" + integrity sha512-Dk997v9ZCt3X/npqzyGdTlq6t7lDBhZwGvV94PKzDArjp7BTRm7WlDAXYd/OWdeFHO8OChQYRJNJvUCqCbrtKA== + dependencies: + browserslist "^4.23.0" + core-js-compat@^3.6.2: version "3.6.5" resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz" @@ -8168,6 +9009,16 @@ cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +cosmiconfig@^8.1.3: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + crc@^3.5.0: version "3.8.0" resolved "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz" @@ -8331,6 +9182,22 @@ css-tree@^1.1.2, css-tree@^1.1.3: mdn-data "2.0.14" source-map "^0.6.1" +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + css-what@2.1: version "2.1.3" resolved "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz" @@ -8434,6 +9301,13 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2" +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== + dependencies: + css-tree "~2.2.0" + cssom@^0.4.4: version "0.4.4" resolved "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz" @@ -8608,6 +9482,11 @@ deepmerge@^4.2.2: resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== +deepmerge@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + default-gateway@^6.0.3: version "6.0.3" resolved "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz" @@ -12817,6 +13696,11 @@ jsonfile@^6.0.1: optionalDependencies: graceful-fs "^4.1.6" +jsonschema@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.4.1.tgz#cc4c3f0077fb4542982973d8a083b6b34f482dab" + integrity sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ== + "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.1.0.tgz" @@ -13337,6 +14221,16 @@ mdn-data@2.0.14: resolved "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + mdurl@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz" @@ -16742,6 +17636,14 @@ snake-case@^2.1.0: dependencies: no-case "^2.2.0" +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + sockjs@^0.3.24: version "0.3.24" resolved "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz" @@ -16806,6 +17708,11 @@ source-list-map@^2.0.0: resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + source-map-resolve@^0.5.2: version "0.5.3" resolved "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz" @@ -17406,6 +18313,19 @@ svgo@^2.7.0, svgo@^2.8.0: picocolors "^1.0.0" stable "^0.1.8" +svgo@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.2.0.tgz#7a5dff2938d8c6096e00295c2390e8e652fa805d" + integrity sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.3.1" + css-what "^6.1.0" + csso "^5.0.5" + picocolors "^1.0.0" + swap-case@^1.1.0: version "1.1.2" resolved "https://registry.npmjs.org/swap-case/-/swap-case-1.1.2.tgz"