From 24a7b4b48f18c8c144b1308db2741fa2c391b6b8 Mon Sep 17 00:00:00 2001 From: Amine Harty Date: Mon, 15 Jun 2026 15:56:08 +0200 Subject: [PATCH] refactor(scan): make remote simulation flow-aware --- packages/snap/snap.manifest.json | 2 +- .../scanRefresher.test.ts | 44 ++++++++++++++++++- .../scanRefresher.ts | 24 +++++----- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/packages/snap/snap.manifest.json b/packages/snap/snap.manifest.json index d282f7db..b975e772 100644 --- a/packages/snap/snap.manifest.json +++ b/packages/snap/snap.manifest.json @@ -7,7 +7,7 @@ "url": "https://github.com/MetaMask/snap-stellar-wallet.git" }, "source": { - "shasum": "0bXKXi1JKlz9Tl+t7sHnw2HInhPpNtkrTIrUdRD/n4I=", + "shasum": "No0nYkGzUlrCP0uk+f5GacSgu57myPr0DPSIWFhznsw=", "location": { "npm": { "filePath": "dist/bundle.js", diff --git a/packages/snap/src/handlers/cronjob/refreshConfirmationContext/scanRefresher.test.ts b/packages/snap/src/handlers/cronjob/refreshConfirmationContext/scanRefresher.test.ts index 586e1fca..60b4203e 100644 --- a/packages/snap/src/handlers/cronjob/refreshConfirmationContext/scanRefresher.test.ts +++ b/packages/snap/src/handlers/cronjob/refreshConfirmationContext/scanRefresher.test.ts @@ -7,7 +7,10 @@ import { TransactionScanOption, TransactionScanValidationType, } from '../../../services/transaction-scan'; -import { FetchStatus } from '../../../ui/confirmation/api'; +import { + ConfirmationInterfaceKey, + FetchStatus, +} from '../../../ui/confirmation/api'; import { logger } from '../../../utils/logger'; describe('ConfirmationScanRefresher', () => { @@ -48,6 +51,7 @@ describe('ConfirmationScanRefresher', () => { overrides: Parameters[0] = {}, ) { return createConfirmationDataContext({ + interfaceKey: ConfirmationInterfaceKey.SignTransaction, preferences: { useSecurityAlerts: true, simulateOnChainActions: true, @@ -59,7 +63,7 @@ describe('ConfirmationScanRefresher', () => { }); } - it('returns fetched scan data and reschedules on success', async () => { + it('includes simulation and validation for sign-transaction', async () => { const { refresher, transactionScanService } = setup(); const result = await refresher.refresh(createScanContext()); @@ -80,6 +84,42 @@ describe('ConfirmationScanRefresher', () => { }); }); + it.each([ + ConfirmationInterfaceKey.ConfirmSendTransaction, + ConfirmationInterfaceKey.ChangeTrustlineOptIn, + ConfirmationInterfaceKey.ChangeTrustlineOptOut, + ])( + 'skips remote simulation for %s even when simulateOnChainActions is enabled', + async (interfaceKey) => { + const { refresher, transactionScanService } = setup(); + + await refresher.refresh(createScanContext({ interfaceKey })); + + expect(transactionScanService.scanTransaction).toHaveBeenCalledWith({ + ...securityScanRequest, + options: [TransactionScanOption.Validation], + }); + }, + ); + + it('omits simulation for sign-transaction when simulateOnChainActions is disabled', async () => { + const { refresher, transactionScanService } = setup(); + + await refresher.refresh( + createScanContext({ + preferences: { + useSecurityAlerts: true, + simulateOnChainActions: false, + }, + }), + ); + + expect(transactionScanService.scanTransaction).toHaveBeenCalledWith({ + ...securityScanRequest, + options: [TransactionScanOption.Validation], + }); + }); + it('returns error status when scan returns null', async () => { const { refresher, transactionScanService } = setup(); transactionScanService.scanTransaction.mockResolvedValueOnce(null); diff --git a/packages/snap/src/handlers/cronjob/refreshConfirmationContext/scanRefresher.ts b/packages/snap/src/handlers/cronjob/refreshConfirmationContext/scanRefresher.ts index 7854ef8d..f058aec5 100644 --- a/packages/snap/src/handlers/cronjob/refreshConfirmationContext/scanRefresher.ts +++ b/packages/snap/src/handlers/cronjob/refreshConfirmationContext/scanRefresher.ts @@ -10,6 +10,7 @@ import type { TransactionScanService } from '../../../services/transaction-scan' import { TransactionScanOption } from '../../../services/transaction-scan'; import type { ContextWithSecurityScan } from '../../../ui/confirmation/api'; import { + ConfirmationInterfaceKey, ContextWithSecurityScanStruct, FetchStatus, } from '../../../ui/confirmation/api'; @@ -18,8 +19,6 @@ import { createPrefixedLogger } from '../../../utils/logger'; type SecurityScanContext = ConfirmationDataContext & ContextWithSecurityScan; -type SecurityScanPreferences = ContextWithSecurityScan['preferences']; - /** * Refreshes Blockaid security scan / simulation results in the confirmation dialog context. */ @@ -52,7 +51,7 @@ export class ConfirmationScanRefresher implements IConfirmationContextRefresher if (!scanCtx.securityScanRequest) { return false; } - return this.#getScanOptions(scanCtx.preferences).length > 0; + return this.#getScanOptions(scanCtx).length > 0; } recoveryResult( @@ -62,7 +61,7 @@ export class ConfirmationScanRefresher implements IConfirmationContextRefresher if (scanCtx.scanFetchStatus !== FetchStatus.Fetching) { return null; } - const optionsEnabled = this.#getScanOptions(scanCtx.preferences).length > 0; + const optionsEnabled = this.#getScanOptions(scanCtx).length > 0; return { result: { scan: null, @@ -81,7 +80,7 @@ export class ConfirmationScanRefresher implements IConfirmationContextRefresher const scanRequest = scanCtx.securityScanRequest as NonNullable< SecurityScanContext['securityScanRequest'] >; - const options = this.#getScanOptions(scanCtx.preferences); + const options = this.#getScanOptions(scanCtx); try { const scan = await this.#transactionScanService.scanTransaction({ @@ -112,16 +111,21 @@ export class ConfirmationScanRefresher implements IConfirmationContextRefresher return ContextWithSecurityScanStruct.is(ctx); } - #getScanOptions( - preferences: SecurityScanPreferences, - ): TransactionScanOption[] { + #getScanOptions(ctx: SecurityScanContext): TransactionScanOption[] { const options: TransactionScanOption[] = []; - if (preferences.simulateOnChainActions) { + // Remote simulation is only used where there is no local re-validation to + // cover submittability — the dapp sign-transaction flow. Send and + // change-trust rely on local re-validation, so we skip remote simulation + // (it proved unreliable on TRON). + if ( + ctx.preferences.simulateOnChainActions && + ctx.interfaceKey === ConfirmationInterfaceKey.SignTransaction + ) { options.push(TransactionScanOption.Simulation); } - if (preferences.useSecurityAlerts) { + if (ctx.preferences.useSecurityAlerts) { options.push(TransactionScanOption.Validation); }