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

- Report the MetaMask origin as lowercase `metamask` instead of `MetaMask` for MetaMask-initiated operations, so the origin matches the value used by the other non-EVM snaps and granted to the keyring methods, and so transaction scan requests are attributed to `https://metamask.io`. The confirmation UI keeps displaying `MetaMask`. ([#392](https://github.com/MetaMask/internal-snaps/pull/392))

## [4.0.0]

### Added
Expand Down
2 changes: 1 addition & 1 deletion packages/tron-wallet-snap/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 72.63,
branches: 72.69,
functions: 79.95,
lines: 85.85,
statements: 85.86,
Expand Down
8 changes: 8 additions & 0 deletions packages/tron-wallet-snap/src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { BigNumber } from 'bignumber.js';

export const ZERO = BigNumber(0);

/**
* Origin used for operations initiated by MetaMask itself (unified send,
* background tracking), as opposed to a dApp origin. Must stay lowercase to
* match the origin granted to the keyring methods and the other non-EVM snaps.
*/
export const METAMASK_ORIGIN = 'metamask';
Comment thread
Battambang marked this conversation as resolved.

export const ACCOUNT_ACTIVATION_FEE_TRX = BigNumber(1);
export const MEMO_FEE_TRX = BigNumber(1);
export const SUN_IN_TRX = 1_000_000;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { TronWebFactory } from '../../clients/tronweb/TronWebFactory';
import {
FALLBACK_FEE,
FEE_LIMIT,
METAMASK_ORIGIN,
Network,
Networks,
TRACK_TX_INTERVAL,
Expand Down Expand Up @@ -1962,7 +1963,7 @@ describe('ClientRequestHandler - signAndSendTransaction', () => {

expect(mockAnalyticsService.trackTransactionSubmitted).toHaveBeenCalledWith(
{
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
accountType: 'tron:eoa',
chainIdCaip: scope,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type { TronWebFactory } from '../../clients/tronweb/TronWebFactory';
import {
FALLBACK_FEE,
FEE_LIMIT,
METAMASK_ORIGIN,
Network,
Networks,
TRACK_TX_INTERVAL,
Expand Down Expand Up @@ -411,12 +412,13 @@ export class ClientRequestHandler {
await this.#transactionsService.save(pendingTransaction);

/**
* Origin is 'MetaMask' because client requests come from MetaMask's own
* unified send flow, matching the unified send path and the background
* transaction tracker.
* Client requests come from MetaMask's own unified send flow, matching the
* unified send path and the background transaction tracker. The origin is
* lowercased so it is recognized as MetaMask by the security alerts scan
* and stays consistent with the other non-EVM snaps.
*/
await this.#analyticsService.trackTransactionSubmitted({
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
accountType: account.type,
chainIdCaip: scope,
});
Expand Down Expand Up @@ -692,7 +694,8 @@ export class ClientRequestHandler {

/**
* Show the confirmation UI.
* Origin is 'MetaMask' because client requests come from MetaMask's own unified send flow.
* Client requests come from MetaMask's own unified send flow, so the origin
* is reported as MetaMask.
*/
const confirmed = await this.#confirmationHandler.confirmTransactionRequest(
{
Expand All @@ -703,7 +706,7 @@ export class ClientRequestHandler {
fees,
asset,
accountType: account.type,
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
Comment thread
Battambang marked this conversation as resolved.
transactionRawData: freshTransactionRawData,
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { SnapClient } from '../../clients/snap/SnapClient';
import type { TronHttpClient } from '../../clients/tron-http/TronHttpClient';
import type { TronWebFactory } from '../../clients/tronweb/TronWebFactory';
import {
METAMASK_ORIGIN,
Network,
TRACK_TX_INTERVAL,
TRACK_TX_MAX_ATTEMPTS,
Expand Down Expand Up @@ -138,7 +139,7 @@ function buildMockInterfaceContext(
overrides: Partial<ConfirmTransactionRequestContext> = {},
): ConfirmTransactionRequestContext {
return {
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
scope: Network.Mainnet,
fromAddress: 'TJRabPrwbZy45sbavfcjinPJC18kjpRTv8',
toAddress: 'TQkE4s6hQqxym4fYvtVLNEGPsaAChFqxPk',
Expand Down Expand Up @@ -1029,7 +1030,7 @@ describe('CronHandler', () => {
expect(
mockAnalyticsService.trackTransactionFinalized,
).toHaveBeenCalledWith({
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
accountType: mockAccount.type,
chainIdCaip: Network.Mainnet,
});
Expand Down
8 changes: 6 additions & 2 deletions packages/tron-wallet-snap/src/handlers/cronjob/cronjob.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import type { PriceApiClient } from '../../clients/price-api/PriceApiClient';
import type { SnapClient } from '../../clients/snap/SnapClient';
import type { TronHttpClient } from '../../clients/tron-http/TronHttpClient';
import type { Network } from '../../constants';
import { TRACK_TX_INTERVAL, TRACK_TX_MAX_ATTEMPTS } from '../../constants';
import {
METAMASK_ORIGIN,
TRACK_TX_INTERVAL,
TRACK_TX_MAX_ATTEMPTS,
} from '../../constants';
import type { AccountsService } from '../../services/accounts/AccountsService';
import type { UnencryptedStateValue } from '../../services/state/stateTypes';
import type { TransactionExpirationRefresherService } from '../../services/transaction-expiration-refresher/TransactionExpirationRefresherService';
Expand Down Expand Up @@ -735,7 +739,7 @@ export class CronHandler {

// Track Transaction Finalized event now that transaction is confirmed
await this.#analyticsService.trackTransactionFinalized({
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
accountType: senderAccount.type,
chainIdCaip: scope,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import { BigNumber } from 'bignumber.js';

import type { SnapClient } from '../../clients/snap/SnapClient';
import type { TronWebFactory } from '../../clients/tronweb/TronWebFactory';
import { KnownCaip19Id, Network, Networks, ZERO } from '../../constants';
import {
KnownCaip19Id,
METAMASK_ORIGIN,
Network,
Networks,
ZERO,
} from '../../constants';
import type { AssetEntity, ResourceAsset } from '../../entities/assets';
import { TronMultichainMethod } from '../../handlers/keyring/keyring-types';
import { getIconUrlForKnownAsset } from '../../ui/confirmation/utils/getIconUrlForKnownAsset';
Expand Down Expand Up @@ -420,7 +426,7 @@ describe('ConfirmationHandler', () => {
fees: defaultFees,
asset: mockAsset,
accountType: 'tron:eoa',
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
transactionRawData: mockTransactionRawData,
};

Expand All @@ -433,15 +439,15 @@ describe('ConfirmationHandler', () => {
expect(result).toBe(true);
expect(mockAnalyticsService.trackTransactionAdded).toHaveBeenCalledWith(
{
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
accountType: 'tron:eoa',
chainIdCaip: Network.Mainnet,
},
);
expect(
mockAnalyticsService.trackTransactionApproved,
).toHaveBeenCalledWith({
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
accountType: 'tron:eoa',
chainIdCaip: Network.Mainnet,
});
Expand All @@ -461,7 +467,7 @@ describe('ConfirmationHandler', () => {
expect(
mockAnalyticsService.trackTransactionRejected,
).toHaveBeenCalledWith({
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
accountType: 'tron:eoa',
chainIdCaip: Network.Mainnet,
});
Expand All @@ -471,7 +477,7 @@ describe('ConfirmationHandler', () => {
});
});

it('passes formatted origin and transactionRawData to render', async () => {
it('passes the raw origin and transactionRawData to render', async () => {
await withConfirmationHandler(
async ({ handler, mockSnapClient, mockState }) => {
mockRenderConfirmTransactionRequest.mockResolvedValue(true);
Expand All @@ -485,14 +491,33 @@ describe('ConfirmationHandler', () => {
mockSnapClient,
mockState,
expect.objectContaining({
origin: 'example.com',
origin: 'https://example.com',
transactionRawData: mockTransactionRawData,
}),
);
},
);
});

it('passes the raw MetaMask origin to render so the scan can recognize it', async () => {
await withConfirmationHandler(
async ({ handler, mockSnapClient, mockState }) => {
mockRenderConfirmTransactionRequest.mockResolvedValue(true);

await handler.confirmTransactionRequest({
...defaultParams,
origin: METAMASK_ORIGIN,
});

expect(mockRenderConfirmTransactionRequest).toHaveBeenCalledWith(
mockSnapClient,
mockState,
expect.objectContaining({ origin: METAMASK_ORIGIN }),
);
},
);
});

it('clears the interface ID after render completes', async () => {
await withConfirmationHandler(async ({ handler, mockState }) => {
mockRenderConfirmTransactionRequest.mockResolvedValue(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { Types as TronwebTypes } from 'tronweb';

import type { SnapClient } from '../../clients/snap/SnapClient';
import type { TronWebFactory } from '../../clients/tronweb/TronWebFactory';
import { Networks, ZERO } from '../../constants';
import { Networks, METAMASK_ORIGIN, ZERO } from '../../constants';
import type { Network } from '../../constants';
import type { AssetEntity } from '../../entities/assets';
import { TronMultichainMethod } from '../../handlers/keyring/keyring-types';
Expand All @@ -25,7 +25,6 @@ import { CONFIRM_SIGN_TRANSACTION_INTERFACE_NAME } from '../../ui/confirmation/v
import type { ConfirmSignTransactionContext } from '../../ui/confirmation/views/ConfirmSignTransaction/types';
import { render as renderConfirmTransactionRequest } from '../../ui/confirmation/views/ConfirmTransactionRequest/render';
import { CONFIRM_TRANSACTION_INTERFACE_NAME } from '../../ui/confirmation/views/ConfirmTransactionRequest/types';
import { formatOrigin } from '../../utils/formatOrigin';
import { SignTransactionRequestStruct } from '../../validation/structs';
import type { TronWalletKeyringRequest } from '../../validation/structs';
import { assertTransactionStructure } from '../../validation/transaction';
Expand Down Expand Up @@ -210,7 +209,12 @@ export class ConfirmationHandler {
amount,
fees,
asset,
origin: formatOrigin(origin),
/**
* Pass the raw origin: the confirmation view formats it for display
* itself, and the security scan needs the unformatted value so it can
* still recognize the MetaMask origin.
*/
origin,
accountType,
transactionRawData,
},
Expand Down Expand Up @@ -289,7 +293,7 @@ export class ConfirmationHandler {
scope,
account,
transaction: { rawDataHex: '', type: '' },
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
preferences,
networkImage: TRX_IMAGE_SVG,
scan: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { Types as TronwebTypes } from 'tronweb';

import {
FEE_LIMIT,
METAMASK_ORIGIN,
Network,
Networks,
TRACK_TX_INTERVAL,
Expand Down Expand Up @@ -164,7 +165,7 @@ describe('SendService', () => {
expect(
mockAnalyticsService.trackTransactionSubmitted,
).toHaveBeenCalledWith({
origin: 'MetaMask',
origin: METAMASK_ORIGIN,
accountType: 'tron:eoa',
chainIdCaip: Network.Mainnet,
});
Expand Down
9 changes: 7 additions & 2 deletions packages/tron-wallet-snap/src/services/send/SendService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ import type { TronWeb, Types as TronwebTypes } from 'tronweb';
import type { SnapClient } from '../../clients/snap/SnapClient';
import type { TronWebFactory } from '../../clients/tronweb/TronWebFactory';
import type { Network } from '../../constants';
import { Networks, TRACK_TX_INTERVAL, ZERO } from '../../constants';
import {
METAMASK_ORIGIN,
Networks,
TRACK_TX_INTERVAL,
ZERO,
} from '../../constants';
import type { AssetEntity } from '../../entities/assets';
import { SendErrorCodes } from '../../handlers/clientRequest/types';
import { BackgroundEventMethod } from '../../handlers/cronjob/cronjob';
Expand Down Expand Up @@ -382,7 +387,7 @@ export class SendService {
scope,
fromAccountId,
transaction,
origin = 'MetaMask',
origin = METAMASK_ORIGIN,
}: {
scope: Network;
fromAccountId: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Types as TronwebTypes } from 'tronweb';
import { SecurityAlertsApiClient } from '../../clients/security-alerts-api/SecurityAlertsApiClient';
import type { SecurityAlertSimulationValidationResponse } from '../../clients/security-alerts-api/structs';
import type { SnapClient } from '../../clients/snap/SnapClient';
import { Network } from '../../constants';
import { METAMASK_ORIGIN, Network } from '../../constants';
import { mockLogger } from '../../utils/mockLogger';
import { TransactionScanService } from './TransactionScanService';
import type { TransactionScanResult } from './types';
Expand Down Expand Up @@ -820,4 +820,57 @@ describe('TransactionScanService', () => {
});
});
});

describe('origin normalization', () => {
const createService = (): {
service: TransactionScanService;
mockSecurityAlertsApiClient: jest.Mocked<
Pick<SecurityAlertsApiClient, 'scanTransaction'>
>;
} => {
const mockSecurityAlertsApiClient = createMockSecurityAlertsApiClient({
simulation: { status: 'Success' },
validation: { status: 'Success', result_type: 'Benign' },
});
const mockSnapClient = createMockSnapClient();
const service = new TransactionScanService(
mockSecurityAlertsApiClient as unknown as SecurityAlertsApiClient,
mockSnapClient as unknown as SnapClient,
mockLogger,
mockAnalyticsService,
);

return { service, mockSecurityAlertsApiClient };
};

it('resolves the MetaMask origin to its URL for the scan', async () => {
const { service, mockSecurityAlertsApiClient } = createService();

await service.scanTransaction({
accountAddress: mockAccount.address,
transactionRawData: createWellFormedTransactionRawData(),
origin: METAMASK_ORIGIN,
scope: Network.Mainnet,
});

expect(mockSecurityAlertsApiClient.scanTransaction).toHaveBeenCalledWith(
expect.objectContaining({ origin: 'https://metamask.io' }),
);
});

it('leaves dApp origins untouched for the scan', async () => {
const { service, mockSecurityAlertsApiClient } = createService();

await service.scanTransaction({
accountAddress: mockAccount.address,
transactionRawData: createWellFormedTransactionRawData(),
origin: 'https://example.com',
scope: Network.Mainnet,
});

expect(mockSecurityAlertsApiClient.scanTransaction).toHaveBeenCalledWith(
expect.objectContaining({ origin: 'https://example.com' }),
);
});
});
});
Loading
Loading