diff --git a/.eslintrc.js b/.eslintrc.js index 5e63def89..ac4e5e29f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -29,6 +29,8 @@ module.exports = { "@typescript-eslint/no-unused-vars": ["error"], "no-console": "off", "react/jsx-filename-extension": ["error", { extensions: [".tsx", ".jsx"] }], + "jsdoc/newline-after-description": "off", + "max-len": "off", }, settings: { "import/resolver": { diff --git a/@shared/api/helpers/stellarSdkServer.ts b/@shared/api/helpers/stellarSdkServer.ts index 955fdcdf9..719c200bc 100644 --- a/@shared/api/helpers/stellarSdkServer.ts +++ b/@shared/api/helpers/stellarSdkServer.ts @@ -1,5 +1,18 @@ import { FeeBumpTransaction, Horizon, Transaction } from "stellar-sdk"; +interface HorizonError { + response: { + status: number; + }; +} +const isHorizonError = (val: unknown): val is HorizonError => + typeof val === "object" && + val !== null && + "response" in val && + typeof val.response == "object" && + val.response !== null && + "status" in val.response; + export const getIsAllowHttp = (networkUrl: string) => !networkUrl.includes("https"); @@ -19,8 +32,8 @@ export const submitTx = async ({ try { submittedTx = await server.submitTransaction(tx); - } catch (e) { - if (e.response.status === 504) { + } catch (e: unknown) { + if (isHorizonError(e) && e.response.status === 504) { // in case of 504, keep retrying this tx until submission succeeds or we get a different error // https://developers.stellar.org/api/errors/http-status-codes/horizon-specific/timeout // https://developers.stellar.org/docs/encyclopedia/error-handling diff --git a/@shared/api/internal.ts b/@shared/api/internal.ts index 696ce1b48..8c784a45d 100644 --- a/@shared/api/internal.ts +++ b/@shared/api/internal.ts @@ -23,7 +23,6 @@ import { AccountBalancesInterface, BalanceToMigrate, Balances, - HorizonOperation, MigratableAccount, MigratedAccount, Settings, @@ -442,8 +441,6 @@ export const getAccountIndexerBalances = async ( const contractIds = await getTokenIds(networkDetails.network as NETWORKS); const url = new URL(`${INDEXER_URL}/account-balances/${publicKey}`); url.searchParams.append("network", networkDetails.network); - url.searchParams.append("horizon_url", networkDetails.networkUrl); - url.searchParams.append("soroban_url", networkDetails.sorobanRpcUrl!); for (const id of contractIds) { url.searchParams.append("contract_ids", id); } @@ -455,6 +452,11 @@ export const getAccountIndexerBalances = async ( throw new Error(_err); } + if ("error" in data && (data?.error?.horizon || data?.error?.soroban)) { + const _err = JSON.stringify(data.error); + captureException(`Failed to fetch account balances - ${_err}`); + } + const formattedBalances = {} as NonNullable< AccountBalancesInterface["balances"] >; @@ -666,14 +668,14 @@ export const getIndexerAccountHistory = async ({ }: { publicKey: string; networkDetails: NetworkDetails; -}) => { +}): Promise => { try { const url = new URL( - `${INDEXER_URL}/account-history/${publicKey}?network=${networkDetails.network}&soroban_url=${networkDetails.sorobanRpcUrl}&horizon_url=${networkDetails.networkUrl}`, + `${INDEXER_URL}/account-history/${publicKey}?network=${networkDetails.network}`, ); const response = await fetch(url.href); - const data = (await response.json()) as HorizonOperation; + const data = await response.json(); if (!response.ok) { throw new Error(data); } @@ -1043,6 +1045,7 @@ export const saveSettings = async ({ isValidatingSafeAssetsEnabled: true, isExperimentalModeEnabled: false, isRpcHealthy: false, + userNotification: { enabled: false, message: "" }, settingsState: SettingsState.IDLE, isSorobanPublicEnabled: false, error: "", diff --git a/@shared/api/package.json b/@shared/api/package.json index 53f944877..b89091ef7 100644 --- a/@shared/api/package.json +++ b/@shared/api/package.json @@ -7,7 +7,7 @@ "@stellar/wallet-sdk": "v0.11.0-beta.1", "bignumber.js": "^9.1.1", "prettier": "^2.0.5", - "stellar-sdk": "^11.1.0", + "stellar-sdk": "^11.2.2", "typescript": "~3.7.2", "webextension-polyfill": "^0.10.0" }, diff --git a/@shared/api/types.ts b/@shared/api/types.ts index 098914685..6bd0faf6e 100644 --- a/@shared/api/types.ts +++ b/@shared/api/types.ts @@ -52,6 +52,7 @@ export interface Response { isExperimentalModeEnabled: boolean; isSorobanPublicEnabled: boolean; isRpcHealthy: boolean; + userNotification: UserNotification; settingsState: SettingsState; networkDetails: NetworkDetails; sorobanRpcUrl: string; @@ -149,10 +150,16 @@ export enum SettingsState { SUCCESS = "SUCCESS", } +export interface UserNotification { + enabled: boolean; + message: string; +} + export interface IndexerSettings { settingsState: SettingsState; isSorobanPublicEnabled: boolean; isRpcHealthy: boolean; + userNotification: UserNotification; } export type Settings = { @@ -196,7 +203,7 @@ export type AssetType = Types.AssetBalance | Types.NativeBalance | TokenBalance; export type TokenBalances = SorobanBalance[]; /* eslint-disable camelcase */ -export type HorizonOperation = any; +export type HorizonOperation = Horizon.ServerApi.OperationRecord; /* eslint-enable camelcase */ export interface AccountBalancesInterface { @@ -204,6 +211,7 @@ export interface AccountBalancesInterface { tokensWithNoBalance: string[]; isFunded: boolean | null; subentryCount: number; + error?: { horizon: any; soroban: any }; } export interface AccountHistoryInterface { diff --git a/@shared/constants/package.json b/@shared/constants/package.json index 4be39c19c..991c1778b 100644 --- a/@shared/constants/package.json +++ b/@shared/constants/package.json @@ -3,7 +3,7 @@ "prettier": "@stellar/prettier-config", "version": "1.0.0", "dependencies": { - "stellar-sdk": "^11.1.0", + "stellar-sdk": "^11.2.2", "typescript": "~3.7.2" }, "devDependencies": { diff --git a/@shared/constants/soroban/token.ts b/@shared/constants/soroban/token.ts index 958cdfafc..4cfe97e65 100644 --- a/@shared/constants/soroban/token.ts +++ b/@shared/constants/soroban/token.ts @@ -4,6 +4,17 @@ export enum SorobanTokenInterface { mint = "mint", } +export type ArgsForTokenInvocation = { + from: string; + to: string; + amount: bigint | number; +}; + +export type TokenInvocationArgs = ArgsForTokenInvocation & { + fnName: SorobanTokenInterface; + contractId: string; +}; + // TODO: can we generate this at build time using the cli TS generator? Also should we? export interface SorobanToken { // only currently holds fields we care about diff --git a/@shared/helpers/package.json b/@shared/helpers/package.json index 85836f65b..e3de9a60e 100644 --- a/@shared/helpers/package.json +++ b/@shared/helpers/package.json @@ -4,7 +4,7 @@ "version": "1.0.0", "dependencies": { "bignumber.js": "^9.1.1", - "stellar-sdk": "^11.1.0", + "stellar-sdk": "^11.2.2", "typescript": "~3.7.2" }, "devDependencies": { diff --git a/docs/package.json b/docs/package.json index 8bc15d760..0b0357890 100644 --- a/docs/package.json +++ b/docs/package.json @@ -27,8 +27,8 @@ "@docusaurus/preset-classic": "2.4.1", "@stellar/freighter-api": "^2.0.0", "clsx": "^1.1.1", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-is": "^16.13.1", "styled-components": "^5.0.1" }, diff --git a/extension/e2e-tests/onboarding.test.ts b/extension/e2e-tests/onboarding.test.ts index 6c6a77692..722de0b26 100644 --- a/extension/e2e-tests/onboarding.test.ts +++ b/extension/e2e-tests/onboarding.test.ts @@ -6,6 +6,7 @@ test.beforeEach(async ({ page, extensionId }) => { }); test("Welcome page loads", async ({ page }) => { + await page.locator(".Welcome__column").waitFor(); await expect( page.getByText("Welcome! Is this your first time using Freighter?"), ).toBeVisible(); diff --git a/extension/package.json b/extension/package.json index 0bf51baf7..2f57f995b 100644 --- a/extension/package.json +++ b/extension/package.json @@ -17,7 +17,7 @@ "dependencies": { "@ledgerhq/hw-app-str": "^6.27.1", "@ledgerhq/hw-transport-webusb": "^6.27.1", - "@reduxjs/toolkit": "^1.5.0", + "@reduxjs/toolkit": "1.5.0", "@sentry/browser": "^6.0.0", "@sentry/tracing": "^6.0.0", "@shared/api": "1.0.0", @@ -26,7 +26,7 @@ "@stellar-asset-lists/sdk": "^1.0.0", "@stellar/design-system": "^1.1.2", "@stellar/wallet-sdk": "v0.11.0-beta.1", - "@testing-library/react": "^10.4.8", + "@testing-library/react": "^14.2.1", "@testing-library/user-event": "^7.1.2", "@types/chrome": "^0.0.104", "@types/history": "^4.7.7", @@ -35,6 +35,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-redux": "^7.1.7", "@types/react-router-dom": "^5.1.3", "@types/redux": "^3.6.0", @@ -66,9 +67,9 @@ "prop-types": "^15.7.2", "punycode": "^2.1.1", "qrcode.react": "^1.0.0", - "react": "^17.0.2", + "react": "^18.2.0", "react-copy-to-clipboard": "^5.0.2", - "react-dom": "^17.0.2", + "react-dom": "^18.2.0", "react-i18next": "^11.18.0", "react-redux": "^7.2.6", "react-router-dom": "^5.3.0", @@ -79,7 +80,7 @@ "ses": "^0.18.5", "stellar-hd-wallet": "^0.0.10", "stellar-identicon-js": "^1.0.0", - "stellar-sdk": "^11.1.0", + "stellar-sdk": "^11.2.2", "svg-url-loader": "^5.0.0", "tsconfig-paths-webpack-plugin": "^3.2.0", "tslib": "2.0.0", diff --git a/extension/src/background/ducks/session.ts b/extension/src/background/ducks/session.ts index 976f26576..74234688f 100644 --- a/extension/src/background/ducks/session.ts +++ b/extension/src/background/ducks/session.ts @@ -23,7 +23,8 @@ export const logIn = createAsyncThunk< allAccounts, }; } catch (e) { - return thunkApi.rejectWithValue({ errorMessage: e.message || e }); + const message = e instanceof Error ? e.message : JSON.stringify(e); + return thunkApi.rejectWithValue({ errorMessage: message }); } }); @@ -39,7 +40,8 @@ export const setActivePublicKey = createAsyncThunk< privateKey: "", }; } catch (e) { - return thunkApi.rejectWithValue({ errorMessage: e.message || e }); + const message = e instanceof Error ? e.message : JSON.stringify(e); + return thunkApi.rejectWithValue({ errorMessage: message }); } }); @@ -47,14 +49,14 @@ const initialState = { publicKey: "", privateKey: "", mnemonicPhrase: "", - allAccounts: [] as Array, + allAccounts: [] as Account[], migratedMnemonicPhrase: "", }; interface UiData { publicKey: string; mnemonicPhrase?: string; - allAccounts?: Array; + allAccounts?: Account[]; migratedMnemonicPhrase?: string; } diff --git a/extension/src/background/helpers/account.ts b/extension/src/background/helpers/account.ts index 02cfeefcb..4acbe62d0 100644 --- a/extension/src/background/helpers/account.ts +++ b/extension/src/background/helpers/account.ts @@ -34,8 +34,10 @@ export const getKeyIdList = async () => export const getAccountNameList = async () => { const encodedaccountNameList = - (await localStore.getItem(ACCOUNT_NAME_LIST_ID)) || encodeObject({}); + ((await localStore.getItem(ACCOUNT_NAME_LIST_ID)) as string) || + encodeObject({}); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument return JSON.parse(decodeString(encodedaccountNameList)); }; @@ -46,7 +48,10 @@ export const addAccountName = async ({ keyId: string; accountName: string; }) => { - const accountNameList = await getAccountNameList(); + const accountNameList = (await getAccountNameList()) as Record< + string, + string + >; accountNameList[keyId] = accountName; @@ -103,7 +108,7 @@ export const getIsHardwareWalletActive = async () => ((await localStore.getItem(KEY_ID)) || "").indexOf(HW_PREFIX) > -1; export const getBipPath = async () => { - const keyId = (await localStore.getItem(KEY_ID)) || ""; + const keyId = ((await localStore.getItem(KEY_ID)) as string) || ""; const hwData = (await localStore.getItem(keyId)) || {}; return hwData.bipPath || ""; }; @@ -159,6 +164,20 @@ export const getIsRpcHealthy = async (networkDetails: NetworkDetails) => { return rpcHealth.status === "healthy"; }; +export const getUserNotification = async () => { + let response = { enabled: false, message: "" }; + + try { + const res = await fetch(`${INDEXER_URL}/user-notification`); + response = await res.json(); + } catch (e) { + captureException(`Failed to load user notification - ${JSON.stringify(e)}`); + console.error(e); + } + + return response; +}; + export const getFeatureFlags = async () => { let featureFlags = { useSorobanPublic: false }; @@ -166,9 +185,7 @@ export const getFeatureFlags = async () => { const res = await fetch(`${INDEXER_URL}/feature-flags`); featureFlags = await res.json(); } catch (e) { - captureException( - `Failed to load feature flag for Soroban mainnet - ${JSON.stringify(e)}`, - ); + captureException(`Failed to load feature flag - ${JSON.stringify(e)}`); console.error(e); } @@ -189,25 +206,33 @@ export const subscribeAccount = async (publicKey: string) => { const options = { method: "POST", headers: { + // eslint-disable-next-line "Content-Type": "application/json", }, body: JSON.stringify({ + // eslint-disable-next-line pub_key: publicKey, network: networkDetails.network, }), }; - await fetch(`${INDEXER_URL}/subscription/account`, options); + const res = await fetch(`${INDEXER_URL}/subscription/account`, options); const subsByKeyId = { ...hasAccountSubByKeyId, [keyId]: true, }; - await localStore.setItem(HAS_ACCOUNT_SUBSCRIPTION, subsByKeyId); + + if (res.ok) { + await localStore.setItem(HAS_ACCOUNT_SUBSCRIPTION, subsByKeyId); + } else { + const resJson = (await res.json()) as string; + throw new Error(resJson); + } } catch (e) { console.error(e); - captureException( - `Failed to subscribe account with Mercury - ${JSON.stringify(e)}`, - ); - throw new Error("Error subscribing account"); + // Turn on when Mercury is enabled + // captureException( + // `Failed to subscribe account with Mercury - ${JSON.stringify(e)}`, + // ); } return { publicKey }; @@ -222,21 +247,31 @@ export const subscribeTokenBalance = async ( const options = { method: "POST", headers: { + // eslint-disable-next-line "Content-Type": "application/json", }, body: JSON.stringify({ + // eslint-disable-next-line pub_key: publicKey, + // eslint-disable-next-line contract_id: contractId, network: networkDetails.network, }), }; - await fetch(`${INDEXER_URL}/subscription/token-balance`, options); + const res = await fetch( + `${INDEXER_URL}/subscription/token-balance`, + options, + ); + + if (!res.ok) { + const resJson = (await res.json()) as string; + throw new Error(resJson); + } } catch (e) { console.error(e); captureException( `Failed to subscribe token balance - ${JSON.stringify(e)}`, ); - throw new Error(`Error subscribing to token: ${contractId}`); } }; @@ -248,17 +283,23 @@ export const subscribeTokenHistory = async ( const options = { method: "POST", headers: { + // eslint-disable-next-line "Content-Type": "application/json", }, + // eslint-disable-next-line body: JSON.stringify({ pub_key: publicKey, contract_id: contractId }), }; - await fetch(`${INDEXER_URL}/subscription/token`, options); + const res = await fetch(`${INDEXER_URL}/subscription/token`, options); + + if (!res.ok) { + const resJson = (await res.json()) as string; + throw new Error(resJson); + } } catch (e) { console.error(e); captureException( `Failed to subscribe token history - ${JSON.stringify(e)}`, ); - throw new Error(`Error subscribing to token: ${contractId}`); } }; @@ -273,6 +314,7 @@ export const verifySorobanRpcUrls = async () => { const networksList: NetworkDetails[] = await getNetworksList(); + // eslint-disable-next-line for (let i = 0; i < networksList.length; i += 1) { const networksListDetails = networksList[i]; diff --git a/extension/src/background/helpers/dataStorage.ts b/extension/src/background/helpers/dataStorage.ts index d52caa690..67c450ad4 100644 --- a/extension/src/background/helpers/dataStorage.ts +++ b/extension/src/background/helpers/dataStorage.ts @@ -78,7 +78,7 @@ export const normalizeMigratedData = async () => { const localStore = dataStorageAccess(browserLocalStorage); const localStorageEntries = Object.entries(localStorage); - // eslint-disable-next-line no-plusplus + // eslint-disable-next-line for (let i = 0; i < localStorageEntries.length; i++) { const [key, value] = localStorageEntries[i]; try { @@ -246,6 +246,16 @@ const migrateSorobanRpcUrlNetwork = async () => { } }; +export const resetAccountSubscriptions = async () => { + const localStore = dataStorageAccess(browserLocalStorage); + const storageVersion = (await localStore.getItem(STORAGE_VERSION)) as string; + + 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, {}); + } +}; + export const versionedMigration = async () => { // sequentially call migrations in order to enforce smooth schema upgrades @@ -254,6 +264,7 @@ export const versionedMigration = async () => { await migrateToAccountSubscriptions(); await migrateMainnetSorobanRpcUrlNetworkDetails(); await migrateSorobanRpcUrlNetwork(); + await resetAccountSubscriptions(); }; // Updates storage version diff --git a/extension/src/background/helpers/migration.ts b/extension/src/background/helpers/migration.ts index 46701d493..408ed8786 100644 --- a/extension/src/background/helpers/migration.ts +++ b/extension/src/background/helpers/migration.ts @@ -38,33 +38,33 @@ export const migrateTrustlines = async ({ isMergeSelected, networkPassphrase, }: MigrateTrustLinesParams) => { - if (!trustlineBalances.length) return; + if (!trustlineBalances.length) { + return; + } const trustlineRecipientAccount = await server.loadAccount( newKeyPair.publicKey, ); const txFee = new BigNumber(fee).times(trustlineBalances.length).toString(); - const changeTrustTx = await new TransactionBuilder( - trustlineRecipientAccount, - { - fee: txFee, - networkPassphrase, - }, - ); + const changeTrustTx = new TransactionBuilder(trustlineRecipientAccount, { + fee: txFee, + networkPassphrase, + }); - const removeTrustTx = await new TransactionBuilder(sourceAccount, { + const removeTrustTx = new TransactionBuilder(sourceAccount, { fee: txFee, networkPassphrase, }); - const sendTrustlineBalanceTx = await new TransactionBuilder(sourceAccount, { + const sendTrustlineBalanceTx = new TransactionBuilder(sourceAccount, { fee: txFee, networkPassphrase, }); const recipientSourceKeys = Keypair.fromSecret(newKeyPair.privateKey); + // eslint-disable-next-line for (let i = 0; i < trustlineBalances.length; i += 1) { const bal = trustlineBalances[i]; let asset; diff --git a/extension/src/background/helpers/session.ts b/extension/src/background/helpers/session.ts index 2283f4d5f..04340131d 100644 --- a/extension/src/background/helpers/session.ts +++ b/extension/src/background/helpers/session.ts @@ -5,10 +5,10 @@ const SESSION_LENGTH = 60 * 24; export const SESSION_ALARM_NAME = "session-timer"; export class SessionTimer { - DURATION = 1000 * 60 * SESSION_LENGTH; + duration = 1000 * 60 * SESSION_LENGTH; runningTimeout: null | ReturnType = null; constructor(duration?: number) { - this.DURATION = duration || this.DURATION; + this.duration = duration || this.duration; } startSession() { diff --git a/extension/src/background/index.ts b/extension/src/background/index.ts index b943d3a9d..250462862 100644 --- a/extension/src/background/index.ts +++ b/extension/src/background/index.ts @@ -31,10 +31,16 @@ export const initExtensionMessageListener = (sessionStore: Store) => { browser?.runtime?.onMessage?.addListener(async (request, sender) => { // todo this is kinda ugly let res; - if (Object.values(SERVICE_TYPES).includes(request.type)) { + if (Object.values(SERVICE_TYPES).includes(request.type as SERVICE_TYPES)) { + // eslint-disable-next-line res = await popupMessageListener(request, sessionStore); } - if (Object.values(EXTERNAL_SERVICE_TYPES).includes(request.type)) { + if ( + Object.values(EXTERNAL_SERVICE_TYPES).includes( + request.type as EXTERNAL_SERVICE_TYPES, + ) + ) { + // eslint-disable-next-line res = await freighterApiMessageListener(request, sender, sessionStore); } @@ -44,7 +50,9 @@ export const initExtensionMessageListener = (sessionStore: Store) => { export const initInstalledListener = () => { browser?.runtime?.onInstalled.addListener(async ({ reason, temporary }) => { - if (temporary) return; // skip during development + if (temporary) { + return; // skip during development + } switch (reason) { case "install": await browser.tabs.create({ diff --git a/extension/src/background/messageListener/freighterApiMessageListener.ts b/extension/src/background/messageListener/freighterApiMessageListener.ts index 0e49bd825..19e09eea1 100644 --- a/extension/src/background/messageListener/freighterApiMessageListener.ts +++ b/extension/src/background/messageListener/freighterApiMessageListener.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + import { TransactionBuilder, Networks, @@ -54,13 +56,13 @@ import { const localStore = dataStorageAccess(browserLocalStorage); -interface WINDOW_PARAMS { +interface WindowParams { height: number; type: "popup"; width: number; } -const WINDOW_SETTINGS: WINDOW_PARAMS = { +const WINDOW_SETTINGS: WindowParams = { type: "popup", width: POPUP_WIDTH, height: POPUP_HEIGHT + 32, // include browser frame height, @@ -169,7 +171,7 @@ export const freighterApiMessageListener = ( if (isValidatingMemo || isValidatingSafety) { _operations.forEach((operation: Operation) => { accountData.forEach( - ({ address, tags }: { address: string; tags: Array }) => { + ({ address, tags }: { address: string; tags: string[] }) => { if ( "destination" in operation && address === operation.destination @@ -204,8 +206,8 @@ export const freighterApiMessageListener = ( try { await server.checkMemoRequired(transaction as Transaction); - } catch (e) { - if (e.accountId) { + } catch (e: any) { + if ("accountId" in e) { flaggedKeys[e.accountId] = { ...flaggedKeys[e.accountId], tags: [TRANSACTION_WARNING.memoRequired], @@ -470,3 +472,5 @@ export const freighterApiMessageListener = ( return messageResponder[request.type](); }; + +/* eslint-enable @typescript-eslint/no-unsafe-argument */ diff --git a/extension/src/background/messageListener/popupMessageListener.ts b/extension/src/background/messageListener/popupMessageListener.ts index a9b43a2c8..ed5bd3a67 100644 --- a/extension/src/background/messageListener/popupMessageListener.ts +++ b/extension/src/background/messageListener/popupMessageListener.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ + import { Store } from "redux"; import { Keypair, @@ -71,6 +73,7 @@ import { getIsExperimentalModeEnabled, getIsHardwareWalletActive, getIsRpcHealthy, + getUserNotification, getSavedNetworks, getNetworkDetails, getNetworksList, @@ -117,23 +120,24 @@ import { const numOfPublicKeysToCheck = 5; const sessionTimer = new SessionTimer(); +// eslint-disable-next-line export const responseQueue: Array<(message?: any) => void> = []; -export const transactionQueue: Array = []; -export const blobQueue: Array<{ +export const transactionQueue: Transaction[] = []; +export const blobQueue: { isDomainListedAllowed: boolean; domain: string; tab: browser.Tabs.Tab | undefined; blob: string; url: string; accountToSign: string; -}> = []; +}[] = []; -export const authEntryQueue: Array<{ +export const authEntryQueue: { accountToSign: string; tab: browser.Tabs.Tab | undefined; entry: string; // xdr.SorobanAuthorizationEntry url: string; -}> = []; +}[] = []; interface KeyPair { publicKey: string; @@ -843,10 +847,10 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { const _getLocalStorageAccounts = async (password: string) => { const keyIdList = await getKeyIdList(); const accountNameList = await getAccountNameList(); - const unlockedAccounts = [] as Array; + const unlockedAccounts = [] as Account[]; // for loop to preserve order of accounts - // eslint-disable-next-line no-plusplus + // eslint-disable-next-line for (let i = 0; i < keyIdList.length; i++) { const keyId = keyIdList[i]; let keyStore; @@ -1042,7 +1046,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { return { error: "Session timed out" }; }; - const signBlob = async () => { + const signBlob = () => { const privateKey = privateKeySelector(sessionStore.getState()); if (privateKey.length) { @@ -1050,7 +1054,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { const blob = blobQueue.pop(); const response = blob - ? await sourceKeys.sign(Buffer.from(blob.blob, "base64")) + ? sourceKeys.sign(Buffer.from(blob.blob, "base64")) : null; const blobResponse = responseQueue.pop(); @@ -1064,7 +1068,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { return { error: "Session timed out" }; }; - const signAuthEntry = async () => { + const signAuthEntry = () => { const privateKey = privateKeySelector(sessionStore.getState()); if (privateKey.length) { @@ -1072,7 +1076,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { const authEntry = authEntryQueue.pop(); const response = authEntry - ? await sourceKeys.sign(hash(Buffer.from(authEntry.entry, "base64"))) + ? sourceKeys.sign(hash(Buffer.from(authEntry.entry, "base64"))) : null; const entryResponse = responseQueue.pop(); @@ -1228,6 +1232,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { const networkDetails = await getNetworkDetails(); const featureFlags = await getFeatureFlags(); const isRpcHealthy = await getIsRpcHealthy(networkDetails); + const userNotification = await getUserNotification(); return { allowList: await getAllowList(), @@ -1240,6 +1245,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { networksList: await getNetworksList(), isSorobanPublicEnabled: featureFlags.useSorobanPublic, isRpcHealthy, + userNotification, }; }; @@ -1402,7 +1408,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { }; const getMigratableAccounts = async () => { - const keyIdList = await getKeyIdList(); + const keyIdList = (await getKeyIdList()) as string[]; const mnemonicPhrase = mnemonicPhraseSelector(sessionStore.getState()); const allAccounts = allAccountsSelector(sessionStore.getState()); @@ -1450,8 +1456,9 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { const migratedAccounts = []; const password = passwordSelector(sessionStore.getState()); - if (!password || !migratedMnemonicPhrase) + if (!password || !migratedMnemonicPhrase) { return { error: "Authentication error" }; + } const newWallet = fromMnemonic(migratedMnemonicPhrase); const keyIdList: string = await getKeyIdList(); @@ -1472,6 +1479,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { 5. Start an account session with the destination account so the user can start signing tx's with their newly migrated account */ + // eslint-disable-next-line for (let i = 0; i < balancesToMigrate.length; i += 1) { const { publicKey, @@ -1501,7 +1509,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { }; // eslint-disable-next-line no-await-in-loop - const transaction = await new TransactionBuilder(sourceAccount, { + const transaction = new TransactionBuilder(sourceAccount, { fee, networkPassphrase, }); @@ -1567,7 +1575,7 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { if (isMergeSelected && migratedAccount.isMigrated) { // since we're doing a merge, we can merge the old account into the new one, which will delete the old account // eslint-disable-next-line no-await-in-loop - const mergeTransaction = await new TransactionBuilder(sourceAccount, { + const mergeTransaction = new TransactionBuilder(sourceAccount, { fee, networkPassphrase, }); @@ -1687,3 +1695,5 @@ export const popupMessageListener = (request: Request, sessionStore: Store) => { return messageResponder[request.type](); }; + +/* eslint-enable @typescript-eslint/no-unsafe-argument */ diff --git a/extension/src/background/store.ts b/extension/src/background/store.ts index 72e52fb4d..65fc68a65 100644 --- a/extension/src/background/store.ts +++ b/extension/src/background/store.ts @@ -16,8 +16,10 @@ const sessionStore = dataStorageAccess(browserSessionStorage); export async function loadState() { try { const state = await sessionStore.getItem(REDUX_STORE_KEY); - if (!state) return undefined; - return JSON.parse(state); + if (!state) { + return undefined; + } + return JSON.parse(state as string); } catch (_error) { return undefined; } diff --git a/extension/src/contentScript/helpers/redirectMessagesToBackground.ts b/extension/src/contentScript/helpers/redirectMessagesToBackground.ts index 51aa3b793..f83bedb93 100644 --- a/extension/src/contentScript/helpers/redirectMessagesToBackground.ts +++ b/extension/src/contentScript/helpers/redirectMessagesToBackground.ts @@ -12,18 +12,23 @@ export const redirectMessagesToBackground = () => { async (event) => { const messagedId = event?.data?.messageId || 0; // We only accept messages from ourselves - if (event.source !== window) return; + if (event.source !== window) { + return; + } // only allow external Freighter API calls unless we're in Dev Mode if ( - !Object.keys(EXTERNAL_SERVICE_TYPES).includes(event.data.type) && + !Object.keys(EXTERNAL_SERVICE_TYPES).includes( + event.data.type as string, + ) && !DEV_EXTENSION ) { return; } // Only respond to messages tagged as being from Freighter API - if (!event.data.source || event.data.source !== EXTERNAL_MSG_REQUEST) + if (!event.data.source || event.data.source !== EXTERNAL_MSG_REQUEST) { return; + } // Forward the message on to Background let res = { error: "Unable to send message to extension" }; try { diff --git a/extension/src/helpers/metrics.ts b/extension/src/helpers/metrics.ts index 65fdcb886..93b959358 100644 --- a/extension/src/helpers/metrics.ts +++ b/extension/src/helpers/metrics.ts @@ -9,8 +9,8 @@ import { settingsDataSharingSelector } from "popup/ducks/settings"; import { AccountType } from "@shared/api/types"; import { captureException } from "@sentry/browser"; -type metricHandler = (state: AppState, action: AnyAction) => void; -const handlersLookup: { [key: string]: metricHandler[] } = {}; +type MetricHandler = (state: AppState, action: AnyAction) => void; +const handlersLookup: { [key: string]: MetricHandler[] } = {}; /* * metricsMiddleware is a redux middleware that calls handlers specified to @@ -18,7 +18,7 @@ const handlersLookup: { [key: string]: metricHandler[] } = {}; * of registered handlers and passes the current state and action. These are * intended for metrics emission, nothing else. */ -export function metricsMiddleware(): Middleware<{}, State> { +export function metricsMiddleware(): Middleware { return ({ getState }) => (next) => (action: AnyAction) => { const state = getState(); (handlersLookup[action.type] || []).forEach((handler) => @@ -30,6 +30,7 @@ export function metricsMiddleware(): Middleware<{}, State> { // I can't figure out how to get the properties off a thunk for the ActionType // without creating an intermediate value +// eslint-disable-next-line const dummyThunk = createAsyncThunk("dummy", () => {}); const dummyAction = createAction("also dummy"); type ActionType = @@ -59,7 +60,7 @@ export function registerHandler( } } -interface event { +interface Event { /* eslint-disable camelcase */ event_type: string; event_properties: { [key: string]: any }; @@ -79,11 +80,11 @@ export interface MetricsData { hwFunded: boolean; importedFunded: boolean; freighterFunded: boolean; - unfundedFreighterAccounts: Array; + unfundedFreighterAccounts: string[]; } const METRICS_ENDPOINT = "https://api.amplitude.com/2/httpapi"; -let cache: event[] = []; +let cache: Event[] = []; const uploadMetrics = throttle(async () => { const toUpload = cache; @@ -94,25 +95,35 @@ const uploadMetrics = throttle(async () => { return; } - const amplitudeFetchRes = await fetch(METRICS_ENDPOINT, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - api_key: AMPLITUDE_KEY, - events: toUpload, - }), - }); + try { + const amplitudeFetchRes = await fetch(METRICS_ENDPOINT, { + method: "POST", + headers: { + // eslint-disable-next-line + "Content-Type": "application/json", + }, + body: JSON.stringify({ + // eslint-disable-next-line + api_key: AMPLITUDE_KEY, + events: toUpload, + }), + }); - if (!amplitudeFetchRes.ok) { - const amplitudeFetchResJson = await amplitudeFetchRes.json(); + if (!amplitudeFetchRes.ok) { + const amplitudeFetchResJson = await amplitudeFetchRes.json(); + captureException( + `Error uploading to Amplitude with error: ${JSON.stringify( + amplitudeFetchResJson, + )} | cache size: ${toUpload.length} | cache contents: ${JSON.stringify( + toUpload, + )}`, + ); + } + } catch (e) { captureException( - `Error uploading to Amplitude with error: ${JSON.stringify( - amplitudeFetchResJson, - )} | cache size: ${toUpload.length} | cache contents: ${JSON.stringify( - toUpload, - )}`, + `Amplitude fetch threw error: ${JSON.stringify(e)} | cache size: ${ + toUpload.length + } | cache contents: ${JSON.stringify(toUpload)}`, ); } }, 500); @@ -138,14 +149,16 @@ const getUserId = () => { */ export const emitMetric = (name: string, body?: any) => { const isDataSharingAllowed = settingsDataSharingSelector(store.getState()); - if (!isDataSharingAllowed) return; + if (!isDataSharingAllowed) { + return; + } const metricsData: MetricsData = JSON.parse( localStorage.getItem(METRICS_DATA) || "{}", ); cache.push({ - /* eslint-disable camelcase */ + /* eslint-disable */ event_type: name, event_properties: body, user_id: getUserId(), @@ -154,7 +167,7 @@ export const emitMetric = (name: string, body?: any) => { hw_connected: metricsData.hwExists, secret_key_account: metricsData.importedExists, secret_key_account_funded: metricsData.importedFunded, - /* eslint-enable camelcase */ + /* eslint-enable */ }); uploadMetrics(); }; diff --git a/extension/src/helpers/urls.ts b/extension/src/helpers/urls.ts index 9a1107af9..14b9ad322 100644 --- a/extension/src/helpers/urls.ts +++ b/extension/src/helpers/urls.ts @@ -20,7 +20,7 @@ export interface EntryToSign { accountToSign: string; } -export const encodeObject = (obj: {}) => +export const encodeObject = (obj: object) => btoa(unescape(encodeURIComponent(JSON.stringify(obj)))); export const decodeString = (str: string) => diff --git a/extension/src/popup/App.tsx b/extension/src/popup/App.tsx index 75d8dbf3e..dbf46c11f 100755 --- a/extension/src/popup/App.tsx +++ b/extension/src/popup/App.tsx @@ -37,21 +37,19 @@ export const store = configureStore({ export type AppDispatch = typeof store.dispatch; -export function App() { - return ( - - - - - - - } - > - - - - - ); -} +export const App = () => ( + + + + + + + } + > + + + + +); diff --git a/extension/src/popup/basics/layout/Box/index.tsx b/extension/src/popup/basics/layout/Box/index.tsx index 1a4144fe8..a4be4bccb 100644 --- a/extension/src/popup/basics/layout/Box/index.tsx +++ b/extension/src/popup/basics/layout/Box/index.tsx @@ -22,11 +22,15 @@ export const Box: React.FC = ({ ...props }: BoxProps) => { const customStyle = { + // eslint-disable-next-line ...(gridCellWidth ? { "--Box-grid-cell-width": gridCellWidth } : {}), + // eslint-disable-next-line ...(gapHorizontal ? { "--Box-gap-horizontal": gapHorizontal } : {}), + // eslint-disable-next-line ...(gapVertical ? { "--Box-gap-vertical": gapVertical } : {}), ...(display === "grid" ? { + // eslint-disable-next-line "grid-template-columns": `repeat(auto-fit, ${ gridCellWidth || "100%" })`, diff --git a/extension/src/popup/basics/layout/View/index.tsx b/extension/src/popup/basics/layout/View/index.tsx index a2c0102cd..4588c1746 100644 --- a/extension/src/popup/basics/layout/View/index.tsx +++ b/extension/src/popup/basics/layout/View/index.tsx @@ -186,10 +186,13 @@ const ViewFooter: React.FC = ({ ...props }: ViewFooterProps) => { const customStyle = { + // eslint-disable-next-line ...(customHeight ? { "--View-footer-height": customHeight } : {}), ...(hasExtraPaddingBottom - ? { "--View-footer-padding-bottom": "1.5rem" } + ? // eslint-disable-next-line + { "--View-footer-padding-bottom": "1.5rem" } : {}), + // eslint-disable-next-line ...(customGap ? { "--View-footer-gap": customGap } : {}), } as React.CSSProperties; @@ -241,6 +244,7 @@ export const ViewInset: React.FC = ({ ...props }: ViewInsetProps) => { const customStyle = { + // eslint-disable-next-line ...(hasNoTopPadding ? { "--View-inset-padding-top": "0" } : {}), } as React.CSSProperties; diff --git a/extension/src/popup/components/AutoSave/index.tsx b/extension/src/popup/components/AutoSave/index.tsx index ba273361f..293d99221 100644 --- a/extension/src/popup/components/AutoSave/index.tsx +++ b/extension/src/popup/components/AutoSave/index.tsx @@ -16,6 +16,7 @@ export const AutoSaveFields = ({ debounceMs = 500 }: AutoSaveFieldsProps) => { const formik = useFormikContext(); const [didSaveFail, setDidSaveFail] = useState(false); + // eslint-disable-next-line react-hooks/exhaustive-deps const debouncedSubmit = useCallback( debounce(async (ctx: typeof formik) => { try { diff --git a/extension/src/popup/components/ErrorBoundary/index.tsx b/extension/src/popup/components/ErrorBoundary/index.tsx index a77f5f79b..d1c1385d5 100644 --- a/extension/src/popup/components/ErrorBoundary/index.tsx +++ b/extension/src/popup/components/ErrorBoundary/index.tsx @@ -10,6 +10,7 @@ import { ROUTES } from "popup/constants/routes"; import "./styles.scss"; +// eslint-disable-next-line interface ErrorBoundaryProps {} export class ErrorBoundary extends Component< @@ -20,8 +21,10 @@ export class ErrorBoundary extends Component< return { hasError: true }; } + // eslint-disable-next-line @typescript-eslint/member-ordering constructor(props: ErrorBoundaryProps) { super(props); + // eslint-disable-next-line this.state = { hasError: false, errorString: "" }; } diff --git a/extension/src/popup/components/ErrorTracking/index.tsx b/extension/src/popup/components/ErrorTracking/index.tsx index 6add45eac..dcd8ccea5 100644 --- a/extension/src/popup/components/ErrorTracking/index.tsx +++ b/extension/src/popup/components/ErrorTracking/index.tsx @@ -17,7 +17,7 @@ export const ErrorTracking = () => { tracesSampleRate: 1.0, denyUrls: [ // Amplitude 4xx's on too many Posts, which is expected behavior - /api\.amplitude\.com/i, + /api\.amplitude\.com\/2\/httpapi/i, ], }); } diff --git a/extension/src/popup/components/Onboarding/index.tsx b/extension/src/popup/components/Onboarding/index.tsx index 72a83f59f..3eec33052 100644 --- a/extension/src/popup/components/Onboarding/index.tsx +++ b/extension/src/popup/components/Onboarding/index.tsx @@ -21,8 +21,10 @@ export const Onboarding = ({ }: OnboardingProps) => { const customStyle = { ...(!customWidth && layout === "full" - ? { "--Onboarding-layout-width": "100%" } + ? // eslint-disable-next-line + { "--Onboarding-layout-width": "100%" } : {}), + // eslint-disable-next-line ...(customWidth ? { "--Onboarding-layout-width": customWidth } : {}), } as React.CSSProperties; diff --git a/extension/src/popup/components/WarningMessages/index.tsx b/extension/src/popup/components/WarningMessages/index.tsx index 770dea9f7..8d96b6b03 100644 --- a/extension/src/popup/components/WarningMessages/index.tsx +++ b/extension/src/popup/components/WarningMessages/index.tsx @@ -284,6 +284,7 @@ export const ScamAssetWarning = ({ issuer, image, onClose, + // eslint-disable-next-line onContinue = () => {}, }: { isSendWarning?: boolean; @@ -348,6 +349,7 @@ export const ScamAssetWarning = ({ .toXDR(); if (isHardwareWallet) { + // eslint-disable-next-line await dispatch(startHwSign({ transactionXDR, shouldSubmit: true })); emitMetric(METRIC_NAMES.manageAssetAddUnsafeAsset, { code, issuer }); } else { @@ -553,6 +555,7 @@ export const NewAssetWarning = ({ }); if (isHardwareWallet) { + // eslint-disable-next-line await dispatch(startHwSign({ transactionXDR, shouldSubmit: true })); emitMetric(METRIC_NAMES.manageAssetAddUnsafeAsset, { code, issuer }); } else { @@ -919,9 +922,11 @@ export const UnverifiedTokenTransferWarning = ({ }); const verifiedTokens = [] as string[]; - for (let i = 0; i < verifiedTokenRes.assets.length; i += 1) { + // 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.assets[i].contract) { + if (details[j].contractId === verifiedTokenRes[i].contract) { verifiedTokens.push(details[j].contractId); return; } @@ -969,8 +974,8 @@ const WarningMessageTokenDetails = ({ const tokenDetailsUrl = React.useCallback( (contractId: string) => - `${INDEXER_URL}/token-details/${contractId}?pub_key=${publicKey}&network=${networkDetails.network}&soroban_url=${networkDetails.sorobanRpcUrl}`, - [publicKey, networkDetails.network, networkDetails.sorobanRpcUrl], + `${INDEXER_URL}/token-details/${contractId}?pub_key=${publicKey}&network=${networkDetails.network}`, + [publicKey, networkDetails.network], ); React.useEffect(() => { async function getTokenDetails() { diff --git a/extension/src/popup/components/account/AccountAssets/index.tsx b/extension/src/popup/components/account/AccountAssets/index.tsx index b5ee90cca..a84b8079e 100644 --- a/extension/src/popup/components/account/AccountAssets/index.tsx +++ b/extension/src/popup/components/account/AccountAssets/index.tsx @@ -125,7 +125,7 @@ export const AssetIcon = ({ interface AccountAssetsProps { assetIcons: AssetIcons; - sortedBalances: Array; + sortedBalances: any[]; setSelectedAsset?: (selectedAsset: string) => void; } @@ -153,7 +153,9 @@ export const AccountAssets = ({ code: string; }) => { /* if we retried the toml and their link is still bad, just give up here */ - if (hasIconFetchRetried) return; + if (hasIconFetchRetried) { + return; + } try { const res = await retryAssetIcon({ key, @@ -200,7 +202,7 @@ export const AccountAssets = ({ let amountUnit; if (rb.liquidityPoolId) { issuer = "lp"; - code = getLPShareCode(rb.reserves); + code = getLPShareCode(rb.reserves as Horizon.HorizonApi.Reserve[]); amountUnit = "shares"; } else if (rb.contractId) { issuer = { @@ -215,14 +217,17 @@ export const AccountAssets = ({ } const isLP = issuer === "lp"; - const canonicalAsset = getCanonicalFromAsset(code, issuer?.key); + const canonicalAsset = getCanonicalFromAsset( + code, + issuer?.key as string, + ); const assetDomain = assetDomains[canonicalAsset]; const isScamAsset = !!blockedDomains.domains[assetDomain]; - const bigTotal = new BigNumber(rb.total); + const bigTotal = new BigNumber(rb.total as string); const amountVal = rb.contractId - ? formatTokenAmount(bigTotal, rb.decimals) + ? formatTokenAmount(bigTotal, rb.decimals as number) : bigTotal.toFixed(); return ( diff --git a/extension/src/popup/components/account/AccountHeader/index.tsx b/extension/src/popup/components/account/AccountHeader/index.tsx index 891521abb..390bd8bf7 100644 --- a/extension/src/popup/components/account/AccountHeader/index.tsx +++ b/extension/src/popup/components/account/AccountHeader/index.tsx @@ -26,9 +26,10 @@ import "./styles.scss"; interface AccountHeaderProps { // accountDropDownRef: React.RefObject; - allAccounts: Array; + allAccounts: Account[]; currentAccountName: string; publicKey: string; + setLoading: (isLoading: boolean) => void; } export const AccountHeader = ({ @@ -36,6 +37,7 @@ export const AccountHeader = ({ allAccounts, currentAccountName, publicKey, + setLoading, }: AccountHeaderProps) => { const { t } = useTranslation(); const dispatch = useDispatch(); @@ -93,6 +95,7 @@ export const AccountHeader = ({ allAccounts={allAccounts} publicKey={publicKey} setIsDropdownOpen={setIsDropdownOpen} + setLoading={setLoading} />

diff --git a/extension/src/popup/components/account/AccountList/index.tsx b/extension/src/popup/components/account/AccountList/index.tsx index 6e08d9c10..980375997 100644 --- a/extension/src/popup/components/account/AccountList/index.tsx +++ b/extension/src/popup/components/account/AccountList/index.tsx @@ -14,7 +14,9 @@ export const OptionTag = ({ hardwareWalletType?: WalletType; imported: boolean; }) => { - if (!hardwareWalletType && !imported) return null; + if (!hardwareWalletType && !imported) { + return null; + } return ( • {hardwareWalletType || "Imported"} @@ -29,6 +31,7 @@ interface AccountListItemProps { setIsDropdownOpen: (isDropdownOpen: boolean) => void; imported: boolean; hardwareWalletType?: WalletType; + setLoading?: (isLoading: boolean) => void; } export const AccountListItem = ({ @@ -38,6 +41,7 @@ export const AccountListItem = ({ setIsDropdownOpen, imported, hardwareWalletType = WalletType.NONE, + setLoading, }: AccountListItemProps) => (
  • @@ -60,15 +65,17 @@ export const AccountListItem = ({ ); interface AccounsListProps { - allAccounts: Array; + allAccounts: Account[]; publicKey: string; setIsDropdownOpen: (isDropdownOpen: boolean) => void; + setLoading?: (isLoading: boolean) => void; } export const AccountList = ({ allAccounts, publicKey, setIsDropdownOpen, + setLoading, }: AccounsListProps) => (
    {allAccounts.map( @@ -89,6 +96,7 @@ export const AccountList = ({ imported={imported} hardwareWalletType={hardwareWalletType} key={`${accountPublicKey}-${accountName}`} + setLoading={setLoading} /> ); }, diff --git a/extension/src/popup/components/account/AssetDetail/index.tsx b/extension/src/popup/components/account/AssetDetail/index.tsx index 26590ee91..f65235bc1 100644 --- a/extension/src/popup/components/account/AssetDetail/index.tsx +++ b/extension/src/popup/components/account/AssetDetail/index.tsx @@ -4,7 +4,7 @@ import { BigNumber } from "bignumber.js"; import { useTranslation } from "react-i18next"; import { IconButton, Icon, Notification } from "@stellar/design-system"; -import { HorizonOperation, AssetType, TokenBalance } from "@shared/api/types"; +import { HorizonOperation, AssetType } from "@shared/api/types"; import { NetworkDetails } from "@shared/constants/stellar"; import { getAvailableBalance, @@ -17,7 +17,7 @@ import { } from "popup/helpers/account"; import { useAssetDomain } from "popup/helpers/useAssetDomain"; import { navigateTo } from "popup/helpers/navigate"; -import { formatTokenAmount } from "popup/helpers/soroban"; +import { formatTokenAmount, isContractId } from "popup/helpers/soroban"; import { getAssetFromCanonical } from "helpers/stellar"; import { ROUTES } from "popup/constants/routes"; @@ -39,18 +39,20 @@ import { View } from "popup/basics/layout/View"; import { saveAsset, saveDestinationAsset, + saveIsToken, transactionSubmissionSelector, } from "popup/ducks/transactionSubmission"; import { AppDispatch } from "popup/App"; import { useIsOwnedScamAsset } from "popup/helpers/useIsOwnedScamAsset"; import StellarLogo from "popup/assets/stellar-logo.png"; +import { formatAmount } from "popup/helpers/formatters"; +import { Loading } from "popup/components/Loading"; import "./styles.scss"; -import { formatAmount } from "popup/helpers/formatters"; interface AssetDetailProps { - assetOperations: Array; - accountBalances: Array; + assetOperations: HorizonOperation[]; + accountBalances: AssetType[]; networkDetails: NetworkDetails; publicKey: string; selectedAsset: string; @@ -116,17 +118,19 @@ export const AssetDetail = ({ defaultDetailViewProps, ); - const { assetDomain } = useAssetDomain({ + const { assetDomain, error: assetError } = useAssetDomain({ assetIssuer, }); + const isContract = isContractId(assetIssuer); + if (!assetOperations && !isSorobanAsset) { return null; } - if (assetIssuer && !assetDomain && !isSorobanAsset) { + if (assetIssuer && !assetDomain && !assetError && !isSorobanAsset) { // if we have an asset issuer, wait until we have the asset domain before continuing - return null; + return ; } return isDetailViewShowing ? ( @@ -174,7 +178,7 @@ export const AssetDetail = ({ assetDomain={assetDomain} contractId={ balance && "decimals" in balance - ? (balance as TokenBalance).token.issuer.key + ? balance.token.issuer.key : undefined } /> @@ -183,17 +187,19 @@ export const AssetDetail = ({
    {balance?.total && new BigNumber(balance?.total).toNumber() > 0 ? ( <> - {/* Hide send for Soroban until send work is ready for Soroban tokens */} - {!isSorobanAsset && ( - { - dispatch(saveAsset(selectedAsset)); - navigateTo(ROUTES.sendPayment); - }} - > - {t("SEND")} - - )} + { + dispatch(saveAsset(selectedAsset)); + if (isContract) { + dispatch(saveIsToken(true)); + } else { + dispatch(saveIsToken(false)); + } + navigateTo(ROUTES.sendPayment); + }} + > + {t("SEND")} + {!isSorobanAsset && ( { @@ -242,7 +248,7 @@ export const AssetDetail = ({ ...operation, isPayment: getIsPayment(operation.type), isSwap: getIsSwap(operation), - }; + } as any; // TODO: isPayment/isSwap overload op type return ( { + // eslint-disable-next-line await dispatch(fundAccount(publicKey)); setIsAccountFriendbotFunded(true); }; diff --git a/extension/src/popup/components/accountHistory/HistoryItem/index.tsx b/extension/src/popup/components/accountHistory/HistoryItem/index.tsx index cc85cf179..10588c80c 100644 --- a/extension/src/popup/components/accountHistory/HistoryItem/index.tsx +++ b/extension/src/popup/components/accountHistory/HistoryItem/index.tsx @@ -1,3 +1,6 @@ +/* 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 { captureException } from "@sentry/browser"; import camelCase from "lodash/camelCase"; @@ -73,6 +76,8 @@ export const HistoryItem = ({ setIsDetailViewShowing, }: HistoryItemProps) => { const { t } = useTranslation(); + // Why does Horizon type not include transaction_attr? + const _op = operation as any; const { account, amount, @@ -88,7 +93,7 @@ export const HistoryItem = ({ isCreateExternalAccount = false, isPayment = false, isSwap = false, - } = operation; + } = _op; let sourceAssetCode; if ("source_asset_code" in operation) { sourceAssetCode = operation.source_asset_code; @@ -108,7 +113,7 @@ export const HistoryItem = ({ const isInvokeHostFn = typeI === 24; const transactionDetailPropsBase: TransactionDetailProps = { - operation, + operation: _op, isCreateExternalAccount, isRecipient: false, isPayment, @@ -203,9 +208,10 @@ export const HistoryItem = ({ isPayment: true, operation: { ...operation, + // eslint-disable-next-line asset_type: "native", to: account, - }, + } as any, // TODO: overloaded op type, native not valid operationText: `-${new BigNumber(startingBalance)} XLM`, })); } else if (isInvokeHostFn) { @@ -304,7 +310,7 @@ export const HistoryItem = ({ setIsLoading(false); } else { const response = await fetch( - `${INDEXER_URL}/token-details/${attrs.contractId}?pub_key=${publicKey}&network=${networkDetails.network}&soroban_url=${networkDetails.sorobanRpcUrl}`, + `${INDEXER_URL}/token-details/${attrs.contractId}?pub_key=${publicKey}&network=${networkDetails.network}`, ); if (!response.ok) { @@ -535,3 +541,4 @@ export const HistoryItem = ({
    ); }; +/* eslint-enable @typescript-eslint/no-unsafe-argument */ diff --git a/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx b/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx index 2a3737d32..6ed8181a6 100644 --- a/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx +++ b/extension/src/popup/components/accountHistory/TransactionDetail/index.tsx @@ -6,6 +6,7 @@ import { Button } from "@stellar/design-system"; import { KeyIdenticon } from "popup/components/identicons/KeyIdenticon"; import { SubviewHeader } from "popup/components/SubviewHeader"; import { AssetNetworkInfo } from "popup/components/accountHistory/AssetNetworkInfo"; +import { Loading } from "popup/components/Loading"; import { View } from "popup/basics/layout/View"; import { emitMetric } from "helpers/metrics"; @@ -40,7 +41,9 @@ export const TransactionDetail = ({ operationText, externalUrl, setIsDetailViewShowing, -}: TransactionDetailProps) => { +}: Omit) => { + // Why does transaction_attr not exist on Horizon types? + const _op = operation as any; const { asset_code: assetCode, asset_issuer: assetIssuer, @@ -49,8 +52,8 @@ export const TransactionDetail = ({ to, created_at: createdAt, transaction_attr: { fee_charged: feeCharged, memo }, - } = operation; - const createdAtDateInstance = new Date(Date.parse(createdAt)); + } = _op; + const createdAtDateInstance = new Date(Date.parse(createdAt as string)); const createdAtLocalStrArr = createdAtDateInstance .toLocaleString() .split(" "); @@ -70,12 +73,15 @@ export const TransactionDetail = ({ const { t } = useTranslation(); - const { assetDomain } = useAssetDomain({ + const { assetDomain, error: assetError } = useAssetDomain({ assetIssuer, }); const networkDetails = useSelector(settingsNetworkDetailsSelector); + const showContent = assetIssuer && !assetDomain && !assetError; - return assetIssuer && !assetDomain ? null : ( + return showContent ? ( + + ) : ( setIsDetailViewShowing(false)} @@ -142,7 +148,7 @@ export const TransactionDetail = ({
    {t("Transaction fee")}
    -
    {stroopToXlm(feeCharged).toString()} XLM
    +
    {stroopToXlm(feeCharged as string).toString()} XLM
  • diff --git a/extension/src/popup/components/accountMigration/MigrationStart/index.tsx b/extension/src/popup/components/accountMigration/MigrationStart/index.tsx index 6d7b64db0..3291c4264 100644 --- a/extension/src/popup/components/accountMigration/MigrationStart/index.tsx +++ b/extension/src/popup/components/accountMigration/MigrationStart/index.tsx @@ -23,6 +23,7 @@ export const MigrationStart = () => { const [isConfirmed, setIsConfirmed] = useState(false); const handleContinue = async () => { + // eslint-disable-next-line await dispatch(changeNetwork({ networkName: NETWORK_NAMES.PUBNET })); navigateTo(ROUTES.accountMigrationReviewMigration); }; diff --git a/extension/src/popup/components/accountMigration/ReviewMigration/index.tsx b/extension/src/popup/components/accountMigration/ReviewMigration/index.tsx index 64e24f526..6c6dff364 100644 --- a/extension/src/popup/components/accountMigration/ReviewMigration/index.tsx +++ b/extension/src/popup/components/accountMigration/ReviewMigration/index.tsx @@ -106,7 +106,9 @@ const AccountListItems = ({ useEffect(() => { const acctListItems: AccountListItemRow[] = []; - if (!recommendedFee) return; + if (!recommendedFee) { + return; + } accountList.forEach((acct) => { const acctIsReadyToMigrate = isReadyToMigrate({ xlmBalance: acct.xlmBalance, @@ -249,7 +251,7 @@ export const ReviewMigration = () => { return; } - // eslint-disable-next-line no-plusplus + // eslint-disable-next-line for (let i = 0; i < migratableAccounts.length; i++) { const publicKey = migratableAccounts[i].publicKey; diff --git a/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx b/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx index 3eaa8deca..5485edefb 100644 --- a/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx +++ b/extension/src/popup/components/hardwareConnect/HardwareSign/index.tsx @@ -80,7 +80,7 @@ export const HardwareSign = ({ const res = await dispatch( signWithHardwareWallet({ - transactionXDR: transactionXDR as string, + transactionXDR, networkPassphrase: networkDetails.networkPassphrase, publicKey, bipPath, diff --git a/extension/src/popup/components/identicons/AccountListIdenticon/index.tsx b/extension/src/popup/components/identicons/AccountListIdenticon/index.tsx index 8fc7d2096..fad51c011 100644 --- a/extension/src/popup/components/identicons/AccountListIdenticon/index.tsx +++ b/extension/src/popup/components/identicons/AccountListIdenticon/index.tsx @@ -3,6 +3,7 @@ import { useDispatch } from "react-redux"; import { truncatedPublicKey } from "helpers/stellar"; import { makeAccountActive } from "popup/ducks/accountServices"; +import { resetAccountBalanceStatus } from "popup/ducks/transactionSubmission"; import { IdenticonImg } from "../IdenticonImg"; @@ -15,6 +16,7 @@ interface KeyIdenticonProps { publicKey: string; displayKey?: boolean; setIsDropdownOpen?: (IsDropdownOpen: boolean) => void; + setLoading?: (isLoading: boolean) => void; } export const AccountListIdenticon = ({ @@ -24,17 +26,24 @@ export const AccountListIdenticon = ({ publicKey = "", displayKey = false, setIsDropdownOpen, + setLoading, }: KeyIdenticonProps) => { const dispatch = useDispatch(); const shortPublicKey = truncatedPublicKey(publicKey); const handleMakeAccountActive = () => { - if (!active) { - dispatch(makeAccountActive(publicKey)); + if (setLoading) { + setLoading(true); } + if (setIsDropdownOpen) { setIsDropdownOpen(false); } + + if (!active) { + dispatch(makeAccountActive(publicKey)); + dispatch(resetAccountBalanceStatus()); + } }; return ( diff --git a/extension/src/popup/components/identicons/KeyIdenticon/index.tsx b/extension/src/popup/components/identicons/KeyIdenticon/index.tsx index c0d4b9c13..f62659899 100644 --- a/extension/src/popup/components/identicons/KeyIdenticon/index.tsx +++ b/extension/src/popup/components/identicons/KeyIdenticon/index.tsx @@ -28,13 +28,17 @@ export const KeyIdenticon = ({ const customStyle = { ...(isSmall ? { + // eslint-disable-next-line "--Icon-padding": "0.2rem", + // eslint-disable-next-line "--Icon-dimension": "1.5rem", } : {}), ...(customSize ? { + // eslint-disable-next-line "--Icon-padding": customSize.padding, + // eslint-disable-next-line "--Icon-dimension": customSize.dimension, } : {}), diff --git a/extension/src/popup/components/manageAssets/AddAsset/index.tsx b/extension/src/popup/components/manageAssets/AddAsset/index.tsx index e4e557389..dafca3e52 100644 --- a/extension/src/popup/components/manageAssets/AddAsset/index.tsx +++ b/extension/src/popup/components/manageAssets/AddAsset/index.tsx @@ -80,8 +80,8 @@ export const AddAsset = () => { return ( {({ dirty, errors, isSubmitting, isValid, touched }) => ( -
    - + + @@ -138,7 +138,7 @@ export const AddAsset = () => { {t("Search")} - +
    )}
    diff --git a/extension/src/popup/components/manageAssets/AddAsset/styles.scss b/extension/src/popup/components/manageAssets/AddAsset/styles.scss index 5caf71c9f..69b4720a1 100644 --- a/extension/src/popup/components/manageAssets/AddAsset/styles.scss +++ b/extension/src/popup/components/manageAssets/AddAsset/styles.scss @@ -1,5 +1,5 @@ :root { - --AddAsset--results-height: 22.9rem; + --AddAsset--results-height: 23.2rem; --AddAsset--title-height: 3rem; } @@ -13,6 +13,13 @@ text-transform: uppercase; } + &__FormContainer { + display: flex; + flex-direction: column; + justify-content: space-between; + overflow: hidden; + } + &__results { flex-grow: 1; height: var(--AddAsset--results-height); diff --git a/extension/src/popup/components/manageAssets/AddToken/index.tsx b/extension/src/popup/components/manageAssets/AddToken/index.tsx index 22ffc52ab..45d5e02e2 100644 --- a/extension/src/popup/components/manageAssets/AddToken/index.tsx +++ b/extension/src/popup/components/manageAssets/AddToken/index.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ import React, { useContext, useEffect, @@ -24,6 +25,7 @@ import { getVerifiedTokens, TokenRecord, searchTokenUrl, + getNativeContractDetails, } from "popup/helpers/searchAsset"; import { isContractId } from "popup/helpers/soroban"; @@ -106,97 +108,129 @@ export const AddToken = () => { const [isSearching, setIsSearching] = useState(false); const [hasNoResults, setHasNoResults] = useState(false); const [isVerifiedToken, setIsVerifiedToken] = useState(false); + const [isVerificationInfoShowing, setIsVerificationInfoShowing] = useState( + false, + ); const ResultsRef = useRef(null); const sorobanClient = useContext(SorobanContext); const isAllowListVerificationEnabled = isMainnet(networkDetails) || isTestnet(networkDetails); + /* eslint-disable react-hooks/exhaustive-deps */ const handleSearch = useCallback( debounce(async ({ target: { value: contractId } }) => { - if (!isContractId(contractId)) { + if (!isContractId(contractId as string)) { setAssetRows([]); return; } + + // clear the UI while we work through the flow setIsSearching(true); + setIsVerifiedToken(false); + setIsVerificationInfoShowing(false); + setAssetRows([]); + const nativeContractDetails = getNativeContractDetails(networkDetails); let verifiedTokens = [] as TokenRecord[]; + // step around verification for native contract and unverifiable networks + + if (nativeContractDetails.contract === contractId) { + // override our rules for verification for XLM + setAssetRows([ + { + code: nativeContractDetails.code, + issuer: contractId, + domain: nativeContractDetails.domain, + }, + ]); + setIsSearching(false); + 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; + } + if (isAllowListVerificationEnabled) { + // usual binary case of a token being verified or unverified verifiedTokens = await getVerifiedTokens({ networkDetails, contractId, setIsSearching, }); - } - - setIsSearching(false); - try { - if (verifiedTokens.length) { - setIsVerifiedToken(true); - setAssetRows( - verifiedTokens.map((record: TokenRecord) => ({ - code: record.code, - issuer: record.contract, - image: record.icon, - domain: record.domain, - })), - ); - } else 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, - }, - ]); - } else { - // 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!, - ); - - const res = await fetch(tokenUrl.href); - const resJson = await res.json(); - if (!res.ok) { - throw new Error(JSON.stringify(resJson)); + try { + if (verifiedTokens.length) { + setIsVerifiedToken(true); + setAssetRows( + verifiedTokens.map((record: TokenRecord) => ({ + code: record.code, + issuer: record.contract, + image: record.icon, + domain: record.domain, + })), + ); } else { - setAssetRows([ - { - code: resJson.symbol, - issuer: contractId, - domain: "", - name: resJson.name, - }, - ]); + // 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!, + ); + + const res = await fetch(tokenUrl.href); + const resJson = await res.json(); + if (!res.ok) { + throw new Error(JSON.stringify(resJson)); + } else { + setAssetRows([ + { + code: resJson.symbol, + issuer: contractId, + domain: "", + name: resJson.name, + }, + ]); + } } + } catch (e) { + setAssetRows([]); + captureException( + `Failed to fetch token details - ${JSON.stringify(e)}`, + ); + console.error(e); } - } catch (e) { - setAssetRows([]); - captureException( - `Failed to fetch token details - ${JSON.stringify(e)}`, - ); - console.error(e); } + + setIsVerificationInfoShowing(isAllowListVerificationEnabled); + + setIsSearching(false); }, 500), [], ); @@ -205,7 +239,12 @@ export const AddToken = () => { setHasNoResults(!assetRows.length); }, [assetRows]); + useEffect(() => { + setIsVerificationInfoShowing(isAllowListVerificationEnabled); + }, [isAllowListVerificationEnabled]); + return ( + // eslint-disable-next-line {}}> {({ dirty }) => (
    { ) : null} - {assetRows.length && isAllowListVerificationEnabled ? ( + {assetRows.length && isVerificationInfoShowing ? ( { ) : null} {hasNoResults && dirty && !isSearching ? ( @@ -270,3 +311,4 @@ export const AddToken = () => { ); }; +/* eslint-enable @typescript-eslint/no-unsafe-argument */ diff --git a/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx b/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx index 8828f2fea..b9f28f78a 100644 --- a/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx +++ b/extension/src/popup/components/manageAssets/ChooseAsset/index.tsx @@ -54,6 +54,7 @@ export const ChooseAsset = ({ balances }: ChooseAssetProps) => { // TODO: cache home domain when getting asset icon // https://github.com/stellar/freighter/issues/410 + // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < sortedBalances.length; i += 1) { if (sortedBalances[i].liquidityPoolId) { // eslint-disable-next-line @@ -75,7 +76,7 @@ export const ChooseAsset = ({ balances }: ChooseAssetProps) => { if (issuer?.key) { try { // eslint-disable-next-line no-await-in-loop - domain = await getAssetDomain(issuer.key, networkUrl); + domain = await getAssetDomain(issuer.key as string, networkUrl); } catch (e) { console.error(e); } @@ -84,7 +85,10 @@ export const ChooseAsset = ({ balances }: ChooseAssetProps) => { collection.push({ code, issuer: issuer?.key || "", - image: assetIcons[getCanonicalFromAsset(code, issuer?.key)], + image: + assetIcons[ + getCanonicalFromAsset(code as string, issuer?.key as string) + ], domain, }); // include native asset for asset dropdown selection diff --git a/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx b/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx index 63ec08f39..91161816c 100644 --- a/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx +++ b/extension/src/popup/components/manageAssets/ManageAssetRows/index.tsx @@ -147,6 +147,7 @@ export const ManageAssetRows = ({ }; if (isHardwareWallet) { + // eslint-disable-next-line await dispatch(startHwSign({ transactionXDR, shouldSubmit: true })); trackChangeTrustline(); } else { @@ -335,7 +336,9 @@ export const ManageAssetRows = ({
    {assetRows.map( ({ code = "", domain, image = "", issuer = "", name = "" }) => { - if (!accountBalances.balances) return null; + if (!accountBalances.balances) { + return null; + } const isContract = isContractId(issuer); const canonicalAsset = getCanonicalFromAsset(code, issuer); const isTrustlineActive = @@ -442,6 +445,7 @@ export const ManageAssetRows = ({ {children}
    {}} isActive={showNewAssetWarning || showBlockedDomainWarning} /> diff --git a/extension/src/popup/components/manageAssets/SearchAsset/index.tsx b/extension/src/popup/components/manageAssets/SearchAsset/index.tsx index b65aa8b6d..69cc99117 100644 --- a/extension/src/popup/components/manageAssets/SearchAsset/index.tsx +++ b/extension/src/popup/components/manageAssets/SearchAsset/index.tsx @@ -80,6 +80,7 @@ export const SearchAsset = () => { tomlInfo?: { image: string }; } + /* eslint-disable react-hooks/exhaustive-deps */ const handleSearch = useCallback( debounce(async ({ target: { value: asset } }) => { if (!asset) { @@ -101,6 +102,7 @@ export const SearchAsset = () => { setIsSearching(false); setAssetRows( + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument resJson._embedded.records // only show records that have a domain and domains that don't have just whitespace .filter( @@ -129,6 +131,7 @@ export const SearchAsset = () => { } return ( + // eslint-disable-next-line @typescript-eslint/no-empty-function {}}> {({ dirty }) => ( { setHasNoResults(false); }} > - + @@ -202,7 +205,7 @@ export const SearchAsset = () => { ) : null} - + )} diff --git a/extension/src/popup/components/manageAssets/SearchAsset/styles.scss b/extension/src/popup/components/manageAssets/SearchAsset/styles.scss index 6149766c9..d7b1dcdc2 100644 --- a/extension/src/popup/components/manageAssets/SearchAsset/styles.scss +++ b/extension/src/popup/components/manageAssets/SearchAsset/styles.scss @@ -23,16 +23,12 @@ &__loader { position: absolute; - top: calc(50% - var(--SearchAsset--loader--dimension)); left: calc(50% - var(--SearchAsset--loader--dimension)); width: var(--SearchAsset--loader--dimension); } &__results { flex-grow: 1; - - &--active { - } } &__copy { diff --git a/extension/src/popup/components/manageAssets/TrustlineError/index.tsx b/extension/src/popup/components/manageAssets/TrustlineError/index.tsx index 8379dcb0a..f7483eab8 100644 --- a/extension/src/popup/components/manageAssets/TrustlineError/index.tsx +++ b/extension/src/popup/components/manageAssets/TrustlineError/index.tsx @@ -132,7 +132,9 @@ export const TrustlineError = ({ }, [error]); useEffect(() => { - if (!balances) return; + if (!balances) { + return; + } const balance = balances[errorAsset]; if (balance) { diff --git a/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx b/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx index 2c20383de..4f84b3f9b 100644 --- a/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx +++ b/extension/src/popup/components/manageNetwork/NetworkForm/index.tsx @@ -274,7 +274,7 @@ export const NetworkForm = ({ isEditing }: NetworkFormProps) => { return ( { handleChange(e); - updatePhrase(e.target); + updatePhrase(e.target as HTMLInputElement); }} wordKey={wordKey} word={convertToWord(wordKey)} diff --git a/extension/src/popup/components/mnemonicPhrase/MnemonicDisplay/index.tsx b/extension/src/popup/components/mnemonicPhrase/MnemonicDisplay/index.tsx index 1e7888e0c..7094c2555 100644 --- a/extension/src/popup/components/mnemonicPhrase/MnemonicDisplay/index.tsx +++ b/extension/src/popup/components/mnemonicPhrase/MnemonicDisplay/index.tsx @@ -6,13 +6,13 @@ import random from "lodash/random"; import "./styles.scss"; -interface generateMnemonicPhraseDisplayProps { +interface GenerateMnemonicPhraseDisplayProps { mnemonicPhrase: string; } export const generateMnemonicPhraseDisplay = ({ mnemonicPhrase = "", -}: generateMnemonicPhraseDisplayProps) => +}: GenerateMnemonicPhraseDisplayProps) => mnemonicPhrase.split(" ").map((word: string) => { /* As a security measure, we want to prevent writing the mnemonic phrase to the DOM. diff --git a/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx b/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx index 4fa9261e8..8b1f6703c 100644 --- a/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx +++ b/extension/src/popup/components/sendPayment/SendAmount/AssetSelect/index.tsx @@ -22,13 +22,13 @@ import { getVerifiedTokens } from "popup/helpers/searchAsset"; import "./styles.scss"; -export function AssetSelect({ +export const AssetSelect = ({ assetCode, issuerKey, }: { assetCode: string; issuerKey: string; -}) { +}) => { const { t } = useTranslation(); const dispatch = useDispatch(); const { assetIcons } = useSelector(transactionSubmissionSelector); @@ -111,9 +111,9 @@ export function AssetSelect({ ); -} +}; -export function PathPayAssetSelect({ +export const PathPayAssetSelect = ({ source, assetCode, issuerKey, @@ -123,7 +123,7 @@ export function PathPayAssetSelect({ assetCode: string; issuerKey: string; balance: string; -}) { +}) => { const dispatch = useDispatch(); const { assetIcons } = useSelector(transactionSubmissionSelector); const isSwap = useIsSwap(); @@ -186,4 +186,4 @@ export function PathPayAssetSelect({ ); -} +}; diff --git a/extension/src/popup/components/sendPayment/SendAmount/SendType/index.tsx b/extension/src/popup/components/sendPayment/SendAmount/SendType/index.tsx index 73bbdd88f..20ef40664 100644 --- a/extension/src/popup/components/sendPayment/SendAmount/SendType/index.tsx +++ b/extension/src/popup/components/sendPayment/SendAmount/SendType/index.tsx @@ -105,6 +105,7 @@ export const SendType = () => { ? PAYMENT_TYPES.REGULAR : PAYMENT_TYPES.PATH_PAYMENT, }} + // eslint-disable-next-line @typescript-eslint/no-empty-function onSubmit={() => {}} > {({ values }) => ( diff --git a/extension/src/popup/components/sendPayment/SendAmount/index.tsx b/extension/src/popup/components/sendPayment/SendAmount/index.tsx index fe3649e82..5b0f180b9 100644 --- a/extension/src/popup/components/sendPayment/SendAmount/index.tsx +++ b/extension/src/popup/components/sendPayment/SendAmount/index.tsx @@ -48,7 +48,7 @@ import { ScamAssetWarning } from "popup/components/WarningMessages"; import { TX_SEND_MAX } from "popup/constants/transaction"; import { BASE_RESERVE } from "@shared/constants/stellar"; -import { BalanceMap } from "@shared/api/types"; +import { BalanceMap, SorobanBalance } from "@shared/api/types"; import "../styles.scss"; enum AMOUNT_ERROR { @@ -142,11 +142,15 @@ export const SendAmount = ({ image: "", }); + /* eslint-disable react-hooks/exhaustive-deps */ const calculateAvailBalance = useCallback( (selectedAsset: string) => { - let availBalance = new BigNumber("0"); + let _availBalance = new BigNumber("0"); if (isToken) { - const tokenBalance = accountBalances?.balances?.[selectedAsset] as any; + // TODO: balances is incorrectly typed and does not include SorobanBalance + const tokenBalance = (accountBalances?.balances?.[ + selectedAsset + ] as any) as SorobanBalance; return getTokenBalance(tokenBalance); } if (accountBalances.balances) { @@ -160,20 +164,20 @@ export const SendAmount = ({ if (selectedAsset === "native") { // needed for different wallet-sdk bignumber.js version const currentBal = new BigNumber(balance.toFixed()); - availBalance = currentBal + _availBalance = currentBal .minus(minBalance) .minus(new BigNumber(Number(recommendedFee))); - if (availBalance.lt(minBalance)) { + if (_availBalance.lt(minBalance)) { return "0"; } } else { // needed for different wallet-sdk bignumber.js version - availBalance = new BigNumber(balance); + _availBalance = new BigNumber(balance); } } - return availBalance.toFixed().toString(); + return _availBalance.toFixed().toString(); }, [ accountBalances.balances, @@ -267,8 +271,9 @@ export const SendAmount = ({ // on asset select get conversion rate useEffect(() => { - if (!formik.values.destinationAsset || Number(formik.values.amount) === 0) + if (!formik.values.destinationAsset || Number(formik.values.amount) === 0) { return; + } setLoadingRate(true); // clear dest amount before re-calculating for UI dispatch(resetDestinationAmount()); @@ -546,6 +551,7 @@ export const SendAmount = ({ {}} isActive={showBlockedDomainWarning} /> diff --git a/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx b/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx index 620b94097..0a97fc971 100644 --- a/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx +++ b/extension/src/popup/components/sendPayment/SendConfirm/SubmitResult/index.tsx @@ -119,6 +119,7 @@ export const SubmitSuccess = ({ viewDetails }: { viewDetails: () => void }) => { }; if (isHardwareWallet) { + // eslint-disable-next-line await dispatch(startHwSign({ transactionXDR, shouldSubmit: true })); trackRemoveTrustline(); } else { @@ -283,7 +284,7 @@ export const SubmitFail = () => {
    {t( "Fees can vary depending on the network congestion. Please try using the suggested fee and try again.", - )} + )}{" "} { title={t("The destination account doesn’t exist")} >
    - {t("Make sure it is a funded Stellar account and try again.")}, + {t("Make sure it is a funded Stellar account and try again.")}{" "} { >
    {t( - "The destination account does not accept the asset you’re sending. The destination account must opt to accept this asset before receiving it.", - )} + "The destination account must opt to accept this asset before receiving it.", + )}{" "} { errorDetails.errorBlock = (
    - {t("Please check the new rate and try again.")} + {t("Please check the new rate and try again.")}{" "} {
    {t( "To create a new account you need to send at least 1 XLM to it.", - )} + )}{" "} { } return errorDetails; }; - const errorDetails = getErrorDetails(error); + const errDetails = getErrorDetails(error); return (
    -
    {errorDetails.title}
    +
    {errDetails.title}
    Icon Fail
    - {errorDetails.status ? `${errorDetails.status}:` : ""}{" "} - {errorDetails.opError} + {errDetails.status ? `Status ${errDetails.status}:` : ""}{" "} + {errDetails.opError}
    -
    - {errorDetails.errorBlock} -
    +
    {errDetails.errorBlock}