Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/tron-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Align transaction security warning copy and scan-loading behavior with user preferences. ([#557](https://github.com/MetaMask/internal-snaps/pull/557))

## [4.3.1]

### Fixed
Expand Down
6 changes: 6 additions & 0 deletions packages/tron-wallet-snap/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@
"confirmation.validationErrorSubtitle": {
"message": "If you approve this request, a third party known for scams will take all your assets."
},
"confirmation.validationWarningTitle": {
"message": "This request may be risky"
},
"confirmation.validationWarningSubtitle": {
"message": "Only continue if you trust this site and/or every address involved."
},
"confirmation.validationErrorLearnMore": {
"message": "Learn more"
},
Expand Down
6 changes: 6 additions & 0 deletions packages/tron-wallet-snap/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@
"confirmation.validationErrorSubtitle": {
"message": "Si aprueba esta solicitud, un tercero conocido por estafas tomará todos sus activos."
},
"confirmation.validationWarningTitle": {
"message": "Esta solicitud puede ser arriesgada"
},
"confirmation.validationWarningSubtitle": {
"message": "Solo continúe si confía en este sitio y/o en todas las direcciones involucradas."
},
Comment thread
Copilot marked this conversation as resolved.
Resolved
"confirmation.validationErrorLearnMore": {
"message": "Más información"
},
Expand Down
6 changes: 6 additions & 0 deletions packages/tron-wallet-snap/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
"confirmation.validationErrorSubtitle": {
"message": "If you approve this request, a third party known for scams will take all your assets."
},
"confirmation.validationWarningTitle": {
"message": "This request may be risky"
},
"confirmation.validationWarningSubtitle": {
"message": "Only continue if you trust this site and/or every address involved."
},
"confirmation.validationErrorLearnMore": {
"message": "Learn more"
},
Expand Down
44 changes: 44 additions & 0 deletions packages/tron-wallet-snap/src/handlers/cronjob/cronjob.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,50 @@ async function withCronHandler(

describe('CronHandler', () => {
describe('refreshConfirmationSend', () => {
it('refreshes transaction validity without scanning when both scan preferences are disabled', async () => {
const context = buildMockInterfaceContext();
const interfaceContext = {
...context,
preferences: {
...context.preferences,
useSecurityAlerts: false,
simulateOnChainActions: false,
},
scanFetchStatus: FetchStatus.Fetched,
};

await withCronHandler(
{ interfaceContext, refreshRawData: true },
async ({ cronHandler, mockSnapClient, mockTransactionScanService }) => {
await cronHandler.refreshConfirmationSend();

expect(
mockTransactionScanService.scanTransaction,
).not.toHaveBeenCalled();
expect(mockSnapClient.updateInterface).toHaveBeenCalledTimes(2);
expect(
mockSnapClient.updateInterface.mock.calls[0]?.[2],
).toMatchObject({
scanFetchStatus: FetchStatus.Fetched,
});
expect(
mockSnapClient.updateInterface.mock.calls[1]?.[2],
).toMatchObject({
scanFetchStatus: FetchStatus.Fetched,
transactionRawData: {
ref_block_bytes: getRefBlockBytes(200_000),
expiration: MOCK_BLOCK_TIMESTAMP + 60_000,
timestamp: MOCK_BLOCK_TIMESTAMP,
},
});
expect(mockSnapClient.scheduleBackgroundEvent).toHaveBeenCalledWith({
method: BackgroundEventMethod.RefreshConfirmationSend,
duration: 'PT20S',
});
},
);
});

it('refreshes security scan and updates interface', async () => {
await withCronHandler(
async ({ cronHandler, mockSnapClient, mockTransactionScanService }) => {
Expand Down
69 changes: 43 additions & 26 deletions packages/tron-wallet-snap/src/handlers/cronjob/cronjob.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ export class CronHandler {
}

/**
* Background job to refresh the security scan for simple send confirmation dialogs.
* Refresh transaction validity and enabled scans for send confirmation dialogs.
*/
async refreshConfirmationSend(): Promise<void> {
this.#logger.info(
Expand Down Expand Up @@ -347,6 +347,11 @@ export class CronHandler {
}

const { preferences, scope, fromAddress, origin } = interfaceContext;

const { useSecurityAlerts, simulateOnChainActions } = preferences;

const shouldRefreshScan = useSecurityAlerts || simulateOnChainActions;

const transactionRawData =
interfaceContext.transactionRawData as JsonTransactionRawData | null;

Expand All @@ -360,7 +365,9 @@ export class CronHandler {
try {
const fetchingContext: ConfirmTransactionRequestContext = {
...interfaceContext,
scanFetchStatus: FetchStatus.Fetching,
scanFetchStatus: shouldRefreshScan
? FetchStatus.Fetching
: interfaceContext.scanFetchStatus,
};

await this.#snapClient.updateInterface(
Expand All @@ -369,16 +376,6 @@ export class CronHandler {
fetchingContext,
);

const options: SecurityAlertsScanOption[] = ['simulation'];
if (preferences.useSecurityAlerts) {
options.push('validation');
}

const scanAccount = {
type: interfaceContext.accountType,
address: fromAddress,
} as ExtendedKeyringAccount;

let { scan } = interfaceContext;
let { scanFetchStatus } = interfaceContext;
let freshTransactionRawData = transactionRawData;
Expand All @@ -390,16 +387,33 @@ export class CronHandler {
rawData: transactionRawData,
});

scan = await this.#transactionScanService.scanTransaction({
accountAddress: fromAddress,
transactionRawData: freshTransactionRawData,
origin,
scope,
options,
account: scanAccount,
});
scanFetchStatus = scan ? FetchStatus.Fetched : FetchStatus.Error;
this.#logger.info('Successfully refreshed send confirmation scan');
if (shouldRefreshScan) {
const options: SecurityAlertsScanOption[] = [];

if (simulateOnChainActions) {
options.push('simulation');
}

if (useSecurityAlerts) {
options.push('validation');
}

const scanAccount = {
type: interfaceContext.accountType,
address: fromAddress,
} as ExtendedKeyringAccount;

scan = await this.#transactionScanService.scanTransaction({
accountAddress: fromAddress,
transactionRawData: freshTransactionRawData,
origin,
scope,
options,
account: scanAccount,
});
scanFetchStatus = scan ? FetchStatus.Fetched : FetchStatus.Error;
this.#logger.info('Successfully refreshed send confirmation scan');
}
} catch (error) {
this.#logger.error('Error refreshing send confirmation scan:', error);
scan = null;
Expand Down Expand Up @@ -487,8 +501,9 @@ export class CronHandler {
const { preferences, scope, account, origin, transaction } =
interfaceContext;

const shouldRefreshScan =
preferences.simulateOnChainActions || preferences.useSecurityAlerts;
const { useSecurityAlerts, simulateOnChainActions } = preferences;

const shouldRefreshScan = simulateOnChainActions || useSecurityAlerts;

try {
const fetchingContext: ConfirmSignTransactionContext = {
Expand Down Expand Up @@ -531,10 +546,12 @@ export class CronHandler {

if (shouldRefreshScan && rawData) {
const options: SecurityAlertsScanOption[] = [];
if (preferences.simulateOnChainActions) {

if (simulateOnChainActions) {
options.push('simulation');
}
if (preferences.useSecurityAlerts) {

if (useSecurityAlerts) {
options.push('validation');
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type { TransactionScanResult } from '../../../services/transaction-scan/t
import { FetchStatus } from '../../../types/snap';
import type { Preferences } from '../../../types/snap';
import { i18n } from '../../../utils/i18n';
import { isFetchStatusLoadingOrFetching } from '../../../utils/isFetchStatusLoadingOrFetching';
import { getErrorMessage } from './getErrorMessage';

/**
Expand Down Expand Up @@ -40,14 +39,14 @@ export function getTransactionAlertProps({
scanFailedMessage: translate('confirmation.simulationMessageAPIError'),
maliciousTitle: translate('confirmation.validationErrorTitle'),
maliciousMessage: translate('confirmation.validationErrorSubtitle'),
warningTitle: translate('confirmation.validationErrorTitle'),
warningMessage: translate('confirmation.validationErrorSubtitle'),
warningTitle: translate('confirmation.validationWarningTitle'),
warningMessage: translate('confirmation.validationWarningSubtitle'),
learnMore: translate('confirmation.validationErrorLearnMore'),
securityAdvisedBy: translate(
'confirmation.validationErrorSecurityAdviced',
),
},
isFetching: isFetchStatusLoadingOrFetching(scanFetchStatus),
isFetching: scanFetchStatus === FetchStatus.Loading,
isFetchError: scanFetchStatus === FetchStatus.Error,
error: scanError
? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,15 +301,18 @@ describe('ConfirmSignTransaction', () => {
return { texts: collectTexts(tree), serialized: JSON.stringify(tree) };
};

it.each([FetchStatus.Loading, FetchStatus.Fetching])(
'renders the scan-in-progress info banner while the scan is %s',
(scanFetchStatus) => {
it.each([
[FetchStatus.Loading, true],
[FetchStatus.Fetching, false],
])(
'sets loading banner visibility when the scan is %s',
(scanFetchStatus, visible) => {
const { texts, serialized } = render(
buildContext({ scanFetchStatus, scan: null }),
);

expect(texts).toContain(SCAN_IN_PROGRESS_TITLE);
expect(serialized).toContain('"severity":"info"');
expect(texts.includes(SCAN_IN_PROGRESS_TITLE)).toBe(visible);
expect(serialized.includes('"severity":"info"')).toBe(visible);
},
);

Expand All @@ -336,7 +339,7 @@ describe('ConfirmSignTransaction', () => {
expect(serialized).toContain('"severity":"danger"');
});

it('renders warning validations as a warning banner', () => {
it('renders the target warning', () => {
const { texts, serialized } = render(
buildContext({
scan: buildScanResult({
Expand All @@ -345,10 +348,34 @@ describe('ConfirmSignTransaction', () => {
}),
);

expect(texts).toContain(MALICIOUS_TITLE);
expect(texts).toContain('This request may be risky');
expect(texts).toContain(
'Only continue if you trust this site and/or every address involved.',
);

expect(serialized).toContain('"severity":"warning"');
expect(serialized).not.toContain('"disabled":true');
});

it.each([
['Warning', 'This request may be risky'],
['Malicious', MALICIOUS_TITLE],
] as const)(
'preserves the %s presentation during background refresh',
(type, title) => {
const { texts, serialized } = render(
buildContext({
scanFetchStatus: FetchStatus.Fetching,
scan: buildScanResult({ validation: { type, reason: null } }),
}),
);

expect(texts).not.toContain(SCAN_IN_PROGRESS_TITLE);
expect(serialized).not.toContain('"disabled":true');
expect(texts).toContain(title);
},
);

it('renders scan errors before validation findings', () => {
const { texts } = render(
buildContext({
Expand All @@ -363,11 +390,14 @@ describe('ConfirmSignTransaction', () => {
expect(texts).not.toContain(MALICIOUS_TITLE);
});

it('renders no banner for benign validations', () => {
const { serialized } = render(buildContext());
it.each([FetchStatus.Fetched, FetchStatus.Fetching])(
'renders no banner for benign validations when the scan is %s',
(scanFetchStatus) => {
const { serialized } = render(buildContext({ scanFetchStatus }));

expect(serialized).not.toContain('"type":"Banner"');
});
expect(serialized).not.toContain('"type":"Banner"');
},
);
});

it('shows unsupported-contract copy for skipped simulations of unknown contracts', () => {
Expand Down
Loading
Loading