From f776da465eff33254530e8d50f948e819545eb57 Mon Sep 17 00:00:00 2001 From: Emmanuel Odii Date: Thu, 12 Nov 2026 16:15:26 +0100 Subject: [PATCH 1/3] fix: label blockaid simulation reverts as likely to fail --- .../__tests__/BlockaidBanner.test.tsx | 6 +++ .../popup/components/BlockaidBanner/index.tsx | 10 +++- .../ReviewTransaction/index.tsx | 13 ++++-- .../components/WarningMessages/index.tsx | 21 +++++++-- .../constants/__tests__/blockaid.test.ts | 15 ++++++ extension/src/popup/constants/blockaid.ts | 6 ++- .../popup/helpers/__tests__/blockaid.test.tsx | 46 ++++++++++++++++++- extension/src/popup/helpers/blockaid.ts | 39 ++++++++++------ .../src/popup/locales/en/translation.json | 3 ++ .../src/popup/locales/pt/translation.json | 3 ++ .../src/popup/views/SignTransaction/index.tsx | 5 +- .../views/__tests__/SignTransaction.test.tsx | 2 +- 12 files changed, 136 insertions(+), 33 deletions(-) diff --git a/extension/src/popup/components/BlockaidBanner/__tests__/BlockaidBanner.test.tsx b/extension/src/popup/components/BlockaidBanner/__tests__/BlockaidBanner.test.tsx index 0f1f1db413..a03c310251 100644 --- a/extension/src/popup/components/BlockaidBanner/__tests__/BlockaidBanner.test.tsx +++ b/extension/src/popup/components/BlockaidBanner/__tests__/BlockaidBanner.test.tsx @@ -32,6 +32,12 @@ describe("getBlockaidBannerTitle", () => { ); }); + it("labels a simulation revert as likely to fail, not suspicious", () => { + expect( + getBlockaidBannerTitle(t, "transaction", SecurityLevel.LIKELY_TO_FAIL), + ).toBe("This transaction is likely to fail"); + }); + it("uses a soft caution title for unable-to-scan (site gets its own copy)", () => { expect( getBlockaidBannerTitle(t, "token", SecurityLevel.UNABLE_TO_SCAN), diff --git a/extension/src/popup/components/BlockaidBanner/index.tsx b/extension/src/popup/components/BlockaidBanner/index.tsx index 7e7d7e564b..1234b988cf 100644 --- a/extension/src/popup/components/BlockaidBanner/index.tsx +++ b/extension/src/popup/components/BlockaidBanner/index.tsx @@ -24,6 +24,7 @@ export type BlockaidEntity = /** * Resolves the banner title from the flagged entity and its severity. * UNABLE_TO_SCAN is a soft "proceed with caution" (site has its own copy). + * LIKELY_TO_FAIL is a simulation revert, not a risk flag. * SAFE never produces a title. */ export const getBlockaidBannerTitle = ( @@ -31,6 +32,10 @@ export const getBlockaidBannerTitle = ( entity: BlockaidEntity, securityLevel: SecurityLevel, ): string => { + if (securityLevel === SecurityLevel.LIKELY_TO_FAIL) { + return t("This transaction is likely to fail"); + } + if (securityLevel === SecurityLevel.UNABLE_TO_SCAN) { return entity === "site" ? t("Unable to scan site for malicious behavior") @@ -73,8 +78,9 @@ interface BlockaidBannerProps { /** * The single reusable Blockaid warning banner. Filled style, colored by - * severity (red for malicious, amber for suspicious / unable-to-scan), with a - * chevron when it opens a detail sheet. Renders nothing for a SAFE verdict. + * severity (red for malicious, amber for suspicious / likely-to-fail / + * unable-to-scan), with a chevron when it opens a detail sheet. Renders + * nothing for a SAFE verdict. */ export const BlockaidBanner = ({ securityLevel, diff --git a/extension/src/popup/components/InternalTransaction/ReviewTransaction/index.tsx b/extension/src/popup/components/InternalTransaction/ReviewTransaction/index.tsx index dc085b300a..834909ec93 100644 --- a/extension/src/popup/components/InternalTransaction/ReviewTransaction/index.tsx +++ b/extension/src/popup/components/InternalTransaction/ReviewTransaction/index.tsx @@ -233,10 +233,11 @@ export const ReviewTx = ({ const isMalicious = securityLevel === SecurityLevel.MALICIOUS; const isSuspicious = securityLevel === SecurityLevel.SUSPICIOUS; - // Determine if a security warning should be shown (tx- or token-driven) + // Determine if a warning should be shown (tx risk, a revert, or a token). const shouldShowTxWarning = isMalicious || isSuspicious || + securityLevel === SecurityLevel.LIKELY_TO_FAIL || securityLevel === SecurityLevel.UNABLE_TO_SCAN; // Banner copy for a flagged destination token (null when the token is clean @@ -289,10 +290,9 @@ export const ReviewTx = ({ // Which single Blockaid banner to render, by priority cascade: // tx-malicious > tx-suspicious > token-malicious > token-suspicious > - // any unable-to-scan. A flagged TOKEN outranks a tx that merely couldn't be - // scanned (common on mainnet), so a malicious-token warning is never - // downgraded to the soft "proceed with caution". Only the tx banner opens - // the expandable pane. + // tx likely-to-fail > any unable-to-scan. A flagged token outranks a revert + // and a tx that merely couldn't be scanned, so a malicious-token warning is + // never downgraded. Only the tx banner opens the expandable pane. const blockaidBannerKind: "tx" | "token" | null = (() => { if ( txSecurityLevel === SecurityLevel.MALICIOUS || @@ -306,6 +306,9 @@ export const ReviewTx = ({ ) { return "token"; } + if (txSecurityLevel === SecurityLevel.LIKELY_TO_FAIL) { + return "tx"; + } if (txSecurityLevel && shouldShowTxWarning) { return "tx"; // tx unable-to-scan } diff --git a/extension/src/popup/components/WarningMessages/index.tsx b/extension/src/popup/components/WarningMessages/index.tsx index b3da93d19a..71c7b1fcd1 100644 --- a/extension/src/popup/components/WarningMessages/index.tsx +++ b/extension/src/popup/components/WarningMessages/index.tsx @@ -25,6 +25,7 @@ import { isBlockaidEnabled, reportAssetWarning, reportTransactionWarning, + scanResultHasSimulationError, useBlockaidOverrideState, useShouldTreatAssetAsUnableToScan, useShouldTreatTxAsUnableToScan, @@ -905,14 +906,26 @@ export const BlockAidScanExpanded = ({ return null; } + // A revert with no risk verdict is a failure prediction. The raw simulation + // error is already one of the rows; the title and subtitle should not call + // it unsafe. + const isSimulationFailureOnly = + !mergedIsMalicious && + !mergedIsSuspicious && + !isAssetScan && + scanResultHasSimulationError(scanResult); const title = mergedIsMalicious ? t("Do not proceed") : mergedIsSuspicious ? t("Suspicious Request") - : t("Proceed with caution"); - const subtitle = isAssetScan - ? t("This token does not appear safe for the following reasons.") - : t("This transaction does not appear safe for the following reasons."); + : isSimulationFailureOnly + ? t("Likely to fail") + : t("Proceed with caution"); + const subtitle = isSimulationFailureOnly + ? t("This transaction is expected to fail for the following reasons.") + : isAssetScan + ? t("This token does not appear safe for the following reasons.") + : t("This transaction does not appear safe for the following reasons."); return (
{ ).toBe(SecurityLevel.MALICIOUS); }); + it("ranks a revert above UNABLE_TO_SCAN and below SUSPICIOUS", () => { + expect( + mergeSecurityLevels([ + SecurityLevel.UNABLE_TO_SCAN, + SecurityLevel.LIKELY_TO_FAIL, + ]), + ).toBe(SecurityLevel.LIKELY_TO_FAIL); + expect( + mergeSecurityLevels([ + SecurityLevel.LIKELY_TO_FAIL, + SecurityLevel.SUSPICIOUS, + ]), + ).toBe(SecurityLevel.SUSPICIOUS); + }); + it("ranks SUSPICIOUS above UNABLE_TO_SCAN", () => { expect( mergeSecurityLevels([ diff --git a/extension/src/popup/constants/blockaid.ts b/extension/src/popup/constants/blockaid.ts index 2b24eb783b..c383b867a7 100644 --- a/extension/src/popup/constants/blockaid.ts +++ b/extension/src/popup/constants/blockaid.ts @@ -7,6 +7,7 @@ export enum SecurityLevel { SUSPICIOUS = "SUSPICIOUS", MALICIOUS = "MALICIOUS", UNABLE_TO_SCAN = "UNABLE_TO_SCAN", + LIKELY_TO_FAIL = "LIKELY_TO_FAIL", } /** @@ -28,8 +29,9 @@ export interface BlockaidWarning { const SECURITY_LEVEL_SEVERITY: Record = { [SecurityLevel.SAFE]: 0, [SecurityLevel.UNABLE_TO_SCAN]: 1, - [SecurityLevel.SUSPICIOUS]: 2, - [SecurityLevel.MALICIOUS]: 3, + [SecurityLevel.LIKELY_TO_FAIL]: 2, + [SecurityLevel.SUSPICIOUS]: 3, + [SecurityLevel.MALICIOUS]: 4, }; /** diff --git a/extension/src/popup/helpers/__tests__/blockaid.test.tsx b/extension/src/popup/helpers/__tests__/blockaid.test.tsx index 71dee7ad37..2752852061 100644 --- a/extension/src/popup/helpers/__tests__/blockaid.test.tsx +++ b/extension/src/popup/helpers/__tests__/blockaid.test.tsx @@ -8,6 +8,7 @@ import { shouldTreatAssetAsUnableToScan, isTxSuspicious, useIsTxSuspicious, + getTransactionSecurityLevel, useShouldTreatTxAsUnableToScan, isBlockaidEnabled, getSiteSecurityStates, @@ -222,7 +223,7 @@ describe("BlockAid Helper Functions", () => { expect(isTxSuspicious(null, null)).toBe(false); }); - it("should return true when simulation has error", () => { + it("should return false when the only finding is a simulation error", () => { expect( isTxSuspicious( { @@ -230,7 +231,7 @@ describe("BlockAid Helper Functions", () => { } as BlockAidScanTxResult, null, ), - ).toBe(true); + ).toBe(false); }); it("should return true when validation result_type is not Benign", () => { @@ -286,6 +287,47 @@ describe("BlockAid Helper Functions", () => { }); }); + describe("getTransactionSecurityLevel", () => { + it("classifies a simulation revert as likely to fail", () => { + expect( + getTransactionSecurityLevel( + { + simulation: { error: "insufficient balance for asset USDC" }, + validation: null, + } as unknown as BlockAidScanTxResult, + false, + null, + ), + ).toBe(SecurityLevel.LIKELY_TO_FAIL); + }); + + it("keeps a malicious verdict when the simulation also reverted", () => { + expect( + getTransactionSecurityLevel( + { + simulation: { error: "insufficient balance for asset USDC" }, + validation: { result_type: "Malicious" }, + } as unknown as BlockAidScanTxResult, + false, + null, + ), + ).toBe(SecurityLevel.MALICIOUS); + }); + + it("keeps a warning verdict as suspicious", () => { + expect( + getTransactionSecurityLevel( + { + simulation: {}, + validation: { result_type: "Warning" }, + } as unknown as BlockAidScanTxResult, + false, + null, + ), + ).toBe(SecurityLevel.SUSPICIOUS); + }); + }); + describe("useIsTxSuspicious hook", () => { beforeEach(() => { process.env.DEV_EXTENSION = "true"; diff --git a/extension/src/popup/helpers/blockaid.ts b/extension/src/popup/helpers/blockaid.ts index ae749b040d..3eb3167bd9 100644 --- a/extension/src/popup/helpers/blockaid.ts +++ b/extension/src/popup/helpers/blockaid.ts @@ -607,11 +607,7 @@ export const isTxSuspicious = ( return false; } - const { simulation, validation } = blockaidData; - - if (simulation && "error" in simulation) { - return true; - } + const { validation } = blockaidData; if ( validation && @@ -726,11 +722,27 @@ export const getSiteSecurityLevel = ({ return SecurityLevel.SAFE; }; +/** + * True when Blockaid's simulation reverted or otherwise failed. The `error` + * string is the revert reason ("insufficient balance for asset …"). This is + * independent of `validation.result_type`, which is the risk verdict. + */ +export const scanResultHasSimulationError = ( + scanResult: { simulation?: unknown } | null | undefined, +): boolean => { + const simulation = scanResult?.simulation; + return ( + !!simulation && typeof simulation === "object" && "error" in simulation + ); +}; + /** * Collapses a transaction Blockaid scan into a single SecurityLevel (or null - * when nothing is flagged) for the reusable BlockaidBanner. A simulation error - * is treated as suspicious. The dev override takes precedence. Shared by the - * swap review and the dApp sign-transaction flow so both read one source. + * when nothing is flagged) for the reusable BlockaidBanner. A risk verdict + * wins over a simulation revert: Malicious and Warning stay malicious / + * suspicious, and a revert with no risk verdict is LIKELY_TO_FAIL. The dev + * override takes precedence. Shared by the swap review and the dApp + * sign-transaction flow so both read one source. */ export const getTransactionSecurityLevel = ( txScanResult: BlockAidScanTxResult | null | undefined, @@ -746,12 +758,7 @@ export const getTransactionSecurityLevel = ( return isUnableToScan ? SecurityLevel.UNABLE_TO_SCAN : null; } - const { simulation, validation } = txScanResult; - - // Handle simulation error - treat as suspicious - if (simulation && "error" in simulation) { - return SecurityLevel.SUSPICIOUS; - } + const { validation } = txScanResult; if (validation && "result_type" in validation) { const resultType = validation.result_type; @@ -763,6 +770,10 @@ export const getTransactionSecurityLevel = ( } } + if (scanResultHasSimulationError(txScanResult)) { + return SecurityLevel.LIKELY_TO_FAIL; + } + if (isUnableToScan) { return SecurityLevel.UNABLE_TO_SCAN; } diff --git a/extension/src/popup/locales/en/translation.json b/extension/src/popup/locales/en/translation.json index 45fc6b4ac5..7a4975a88d 100644 --- a/extension/src/popup/locales/en/translation.json +++ b/extension/src/popup/locales/en/translation.json @@ -370,6 +370,7 @@ "Leave feedback about Blockaid warnings and messages": "Leave feedback about Blockaid warnings and messages", "Ledger will not display the transaction details in the device display prior to signing so make sure you only interact with applications you know and trust.": "Ledger will not display the transaction details in the device display prior to signing so make sure you only interact with applications you know and trust.", "Let’s go": "Let’s go", + "Likely to fail": "Likely to fail", "Limit": "Limit", "Limited trading data": "Limited trading data", "Links": "Links", @@ -703,6 +704,8 @@ "This transaction disables your account's master key. You may permanently lose access to this account unless another signer with sufficient weight is added.": "This transaction disables your account's master key. You may permanently lose access to this account unless another signer with sufficient weight is added.", "This transaction does not appear safe for the following reasons": "This transaction does not appear safe for the following reasons", "This transaction does not appear safe for the following reasons.": "This transaction does not appear safe for the following reasons.", + "This transaction is expected to fail for the following reasons.": "This transaction is expected to fail for the following reasons.", + "This transaction is likely to fail": "This transaction is likely to fail", "This transaction was flagged as malicious": "This transaction was flagged as malicious", "This transaction was flagged as malicious (override active)": "This transaction was flagged as malicious (override active)", "This transaction was flagged as suspicious": "This transaction was flagged as suspicious", diff --git a/extension/src/popup/locales/pt/translation.json b/extension/src/popup/locales/pt/translation.json index 34b51ea54c..294fa544ac 100644 --- a/extension/src/popup/locales/pt/translation.json +++ b/extension/src/popup/locales/pt/translation.json @@ -370,6 +370,7 @@ "Leave feedback about Blockaid warnings and messages": "Deixar feedback sobre avisos e mensagens do Blockaid", "Ledger will not display the transaction details in the device display prior to signing so make sure you only interact with applications you know and trust.": "O Ledger não exibirá os detalhes da transação na tela do dispositivo antes de assinar, então certifique-se de interagir apenas com aplicativos que você conhece e confia.", "Let’s go": "Vamos lá", + "Likely to fail": "Provável falha", "Limit": "Limite", "Limited trading data": "Dados de negociação limitados", "Links": "Links", @@ -703,6 +704,8 @@ "This transaction disables your account's master key. You may permanently lose access to this account unless another signer with sufficient weight is added.": "Esta transação desativa a chave mestra da sua conta. Você pode perder permanentemente o acesso a esta conta, a menos que outro assinante com peso suficiente seja adicionado.", "This transaction does not appear safe for the following reasons": "Esta transação não parece segura pelos seguintes motivos", "This transaction does not appear safe for the following reasons.": "Esta transação não parece segura pelos seguintes motivos.", + "This transaction is expected to fail for the following reasons.": "Esta transação deve falhar pelos seguintes motivos.", + "This transaction is likely to fail": "Esta transação provavelmente vai falhar", "This transaction was flagged as malicious": "Esta transação foi marcada como maliciosa", "This transaction was flagged as malicious (override active)": "Esta transação foi sinalizada como maliciosa (substituição ativa)", "This transaction was flagged as suspicious": "Esta transação foi marcada como suspeita", diff --git a/extension/src/popup/views/SignTransaction/index.tsx b/extension/src/popup/views/SignTransaction/index.tsx index 2f06c75b38..05da4ace29 100644 --- a/extension/src/popup/views/SignTransaction/index.tsx +++ b/extension/src/popup/views/SignTransaction/index.tsx @@ -60,6 +60,7 @@ import { useIsTxSuspicious, getSiteSecurityStates, getTransactionSecurityLevel, + scanResultHasSimulationError, } from "popup/helpers/blockaid"; import { HardwareSign } from "popup/components/hardwareConnect/HardwareSign"; import { Loading } from "popup/components/Loading"; @@ -322,8 +323,7 @@ export const SignTransaction = () => { (scanResult.validation.result_type === "Malicious" || scanResult.validation.result_type === "Warning") ); - const hasSimulationError = - scanResult && scanResult.simulation && "error" in scanResult.simulation; + const hasSimulationError = scanResultHasSimulationError(scanResult); const showBlockAidDetails = isUnableToScan || hasSimulationError || @@ -333,7 +333,6 @@ export const SignTransaction = () => { (scanResult?.validation && "result_type" in scanResult.validation && scanResult.validation.result_type === "Malicious") || - hasSimulationError || isSiteMalicious; // Single Blockaid verdict for the banner: the worst of the transaction scan diff --git a/extension/src/popup/views/__tests__/SignTransaction.test.tsx b/extension/src/popup/views/__tests__/SignTransaction.test.tsx index 31edaedf2d..b4b9d492f7 100644 --- a/extension/src/popup/views/__tests__/SignTransaction.test.tsx +++ b/extension/src/popup/views/__tests__/SignTransaction.test.tsx @@ -1190,7 +1190,7 @@ describe("SignTransactions", () => { ); await waitFor(() => screen.getByTestId("SignTransaction")); expect(screen.getByTestId("sign-tx-blockaid-banner")).toHaveTextContent( - "This transaction was flagged as suspicious", + "This transaction is likely to fail", ); expect(screen.getByText("Confirm anyway")).toBeDefined(); }); From 60d2f87918c3a9cdd5fe7944252763dcdf388303 Mon Sep 17 00:00:00 2001 From: Emmanuel Odii Date: Thu, 12 Nov 2026 18:31:32 +0100 Subject: [PATCH 2/3] refactor: check simulation errors inline instead of a helper --- .../components/WarningMessages/index.tsx | 6 ++++-- extension/src/popup/helpers/blockaid.ts | 19 +++---------------- .../src/popup/views/SignTransaction/index.tsx | 5 +++-- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/extension/src/popup/components/WarningMessages/index.tsx b/extension/src/popup/components/WarningMessages/index.tsx index 71c7b1fcd1..126052fe7a 100644 --- a/extension/src/popup/components/WarningMessages/index.tsx +++ b/extension/src/popup/components/WarningMessages/index.tsx @@ -25,7 +25,6 @@ import { isBlockaidEnabled, reportAssetWarning, reportTransactionWarning, - scanResultHasSimulationError, useBlockaidOverrideState, useShouldTreatAssetAsUnableToScan, useShouldTreatTxAsUnableToScan, @@ -913,7 +912,10 @@ export const BlockAidScanExpanded = ({ !mergedIsMalicious && !mergedIsSuspicious && !isAssetScan && - scanResultHasSimulationError(scanResult); + !!scanResult && + "simulation" in scanResult && + !!scanResult.simulation && + "error" in scanResult.simulation; const title = mergedIsMalicious ? t("Do not proceed") : mergedIsSuspicious diff --git a/extension/src/popup/helpers/blockaid.ts b/extension/src/popup/helpers/blockaid.ts index 3eb3167bd9..12fb1fe2a5 100644 --- a/extension/src/popup/helpers/blockaid.ts +++ b/extension/src/popup/helpers/blockaid.ts @@ -722,20 +722,6 @@ export const getSiteSecurityLevel = ({ return SecurityLevel.SAFE; }; -/** - * True when Blockaid's simulation reverted or otherwise failed. The `error` - * string is the revert reason ("insufficient balance for asset …"). This is - * independent of `validation.result_type`, which is the risk verdict. - */ -export const scanResultHasSimulationError = ( - scanResult: { simulation?: unknown } | null | undefined, -): boolean => { - const simulation = scanResult?.simulation; - return ( - !!simulation && typeof simulation === "object" && "error" in simulation - ); -}; - /** * Collapses a transaction Blockaid scan into a single SecurityLevel (or null * when nothing is flagged) for the reusable BlockaidBanner. A risk verdict @@ -758,7 +744,7 @@ export const getTransactionSecurityLevel = ( return isUnableToScan ? SecurityLevel.UNABLE_TO_SCAN : null; } - const { validation } = txScanResult; + const { simulation, validation } = txScanResult; if (validation && "result_type" in validation) { const resultType = validation.result_type; @@ -770,7 +756,8 @@ export const getTransactionSecurityLevel = ( } } - if (scanResultHasSimulationError(txScanResult)) { + // A revert is "this will fail". It is not a risk verdict. + if (simulation && "error" in simulation) { return SecurityLevel.LIKELY_TO_FAIL; } diff --git a/extension/src/popup/views/SignTransaction/index.tsx b/extension/src/popup/views/SignTransaction/index.tsx index 05da4ace29..b7268dc0e0 100644 --- a/extension/src/popup/views/SignTransaction/index.tsx +++ b/extension/src/popup/views/SignTransaction/index.tsx @@ -60,7 +60,6 @@ import { useIsTxSuspicious, getSiteSecurityStates, getTransactionSecurityLevel, - scanResultHasSimulationError, } from "popup/helpers/blockaid"; import { HardwareSign } from "popup/components/hardwareConnect/HardwareSign"; import { Loading } from "popup/components/Loading"; @@ -323,7 +322,9 @@ export const SignTransaction = () => { (scanResult.validation.result_type === "Malicious" || scanResult.validation.result_type === "Warning") ); - const hasSimulationError = scanResultHasSimulationError(scanResult); + const hasSimulationError = !!( + scanResult?.simulation && "error" in scanResult.simulation + ); const showBlockAidDetails = isUnableToScan || hasSimulationError || From 1e9de9d324d792be9723fb8d92ba22ecfbba43b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Nov 2026 23:10:32 +0000 Subject: [PATCH 3/3] fix: keep site verdict in blockaid details sheet on sign The sign-transaction details sheet only received the transaction scan, so a benign-but-reverting transaction from a malicious site opened a "Likely to fail" sheet that never mentioned the site risk the banner reported. Forward the site severity and reason via extraSeverityLevel and extraWarnings. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01R8cp3zXi8eMsEZ4XmFe6K6 --- .../src/popup/views/SignTransaction/index.tsx | 24 ++- .../views/__tests__/SignTransaction.test.tsx | 138 ++++++++++++++++++ 2 files changed, 161 insertions(+), 1 deletion(-) diff --git a/extension/src/popup/views/SignTransaction/index.tsx b/extension/src/popup/views/SignTransaction/index.tsx index b7268dc0e0..33dcbc4b2c 100644 --- a/extension/src/popup/views/SignTransaction/index.tsx +++ b/extension/src/popup/views/SignTransaction/index.tsx @@ -54,7 +54,11 @@ import { MemoRequiredLabel, } from "popup/components/WarningMessages"; import { BlockaidBanner } from "popup/components/BlockaidBanner"; -import { SecurityLevel, mergeSecurityLevels } from "popup/constants/blockaid"; +import { + BlockaidWarning, + SecurityLevel, + mergeSecurityLevels, +} from "popup/constants/blockaid"; import { useShouldTreatTxAsUnableToScan, useIsTxSuspicious, @@ -357,6 +361,22 @@ export const SignTransaction = () => { ]); const blockaidBannerEntity = txSecurityLevel ? "transaction" : "site"; + // The details sheet only receives the transaction scan, so carry the site + // verdict into it. Otherwise a reverting tx from a malicious site would open + // a "Likely to fail" sheet that never mentions the site risk the banner + // reported. + const isSiteFlagged = isSiteMalicious || isSiteSuspicious; + const blockaidSheetSiteWarnings: BlockaidWarning[] = isSiteFlagged + ? [ + { + description: isSiteMalicious + ? t("This site was flagged as malicious") + : t("This site was flagged as suspicious"), + isError: isSiteMalicious, + }, + ] + : []; + if (_networkPassphrase !== networkPassphrase) { return ( { {isOnBlockaidSheet ? ( setIsOnBlockaidSheet(false)} /> ) : ( diff --git a/extension/src/popup/views/__tests__/SignTransaction.test.tsx b/extension/src/popup/views/__tests__/SignTransaction.test.tsx index b4b9d492f7..338a75250a 100644 --- a/extension/src/popup/views/__tests__/SignTransaction.test.tsx +++ b/extension/src/popup/views/__tests__/SignTransaction.test.tsx @@ -35,6 +35,7 @@ import { RequestState } from "constants/request"; import { TRANSACTION_WARNING } from "constants/transaction"; import { AppDataType } from "helpers/hooks/useGetAppData"; import * as SigningFlowHooks from "popup/helpers/useSetupSigningFlow"; +import * as BlockaidHelpers from "popup/helpers/blockaid"; import { ShowOverlayStatus } from "popup/ducks/transactionSubmission"; import { WalletType } from "@shared/constants/hardwareWallet"; import { sortBalances } from "popup/helpers/account"; @@ -1194,6 +1195,143 @@ describe("SignTransactions", () => { ); expect(screen.getByText("Confirm anyway")).toBeDefined(); }); + it("keeps the malicious site verdict in the details sheet when a benign tx is expected to fail", async () => { + const getSiteSecurityStatesSpy = jest + .spyOn(BlockaidHelpers, "getSiteSecurityStates") + .mockReturnValue({ + isMalicious: true, + isSuspicious: false, + isUnableToScan: false, + }); + let currentSignTxDataMock = { + state: { + state: RequestState.SUCCESS, + data: { + type: AppDataType.RESOLVED, + scanResult: { + simulation: { + status: "Error" as any, + error: "This transaction is expected to fail" as any, + }, + validation: null, + request_id: "1", + }, + icons: {}, + balances: { + balances: sortBalances(mockBalances.balances), + isFunded: true, + subentryCount: 0, + }, + publicKey: mockAccounts[0].publicKey, + signFlowState: { + allAccounts: mockAccounts, + accountNotFound: false, + currentAccount: mockAccounts[0], + }, + applicationState: APPLICATION_STATE.MNEMONIC_PHRASE_CONFIRMED, + networkDetails: { + ...defaultSettingsState.networkDetails, + networkPassphrase: "Test SDF Network ; September 2015", + }, + siteScanData: null, + blockaidOverrideState: null, + }, + error: null, + }, + fetchData: jest.fn(), + } as ReturnType; + jest + .spyOn(SignTxDataHooks, "useGetSignTxData") + .mockReturnValue(currentSignTxDataMock); + jest.spyOn(ApiInternal, "loadSettings").mockImplementation(() => + Promise.resolve({ + networkDetails: { + ...defaultSettingsState.networkDetails, + networkPassphrase: "Test SDF Network ; September 2015", + networkName: "Test Net", + }, + networksList: DEFAULT_NETWORKS, + hiddenAssets: {}, + allowList: { + "Test Net": { + [mockAccounts[0].publicKey]: ["laboratory.stellar.org"], + }, + }, + error: "", + isDataSharingAllowed: false, + isMemoValidationEnabled: false, + isHideDustEnabled: true, + isOpenSidebarByDefault: false, + settingsState: SettingsState.SUCCESS, + isSorobanPublicEnabled: false, + isRpcHealthy: true, + userNotification: { + enabled: false, + message: "", + }, + isExperimentalModeEnabled: false, + isHashSigningEnabled: false, + isNonSSLEnabled: false, + experimentalFeaturesState: SettingsState.SUCCESS, + assetsLists: DEFAULT_ASSETS_LISTS, + autoLockTimeoutMinutes: DEFAULT_AUTO_LOCK_TIMEOUT_MINUTES, + }), + ); + const transaction = TransactionBuilder.fromXdr( + transactions.classic, + Networks.TESTNET, + ) as Transaction; + const op = transaction.operations[0]; + jest.spyOn(Stellar, "getTransactionInfo").mockImplementation(() => ({ + ...mockTransactionInfo, + transactionXdr: transactions.classic, + transaction: { + ...mockTransactionInfo.transaction, + _networkPassphrase: Networks.TESTNET, + _operations: [op], + }, + isHttpsDomain: false, + domain: "laboratory.stellar.org", + uuid: "123-123-123-123-123", + })); + render( + + + , + ); + await waitFor(() => screen.getByTestId("SignTransaction")); + fireEvent.click(screen.getByTestId("sign-tx-blockaid-banner")); + await waitFor(() => screen.getByText("Do not proceed")); + expect(screen.queryByText("Likely to fail")).toBeNull(); + expect( + screen.getByText("This site was flagged as malicious"), + ).toBeDefined(); + expect( + screen.getByText("This transaction is expected to fail"), + ).toBeDefined(); + getSiteSecurityStatesSpy.mockRestore(); + }); it("renders blockaid scan label when tx is malicious", async () => { let currentSignTxDataMock = { state: {