Skip to content
Draft
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
2 changes: 2 additions & 0 deletions packages/tron-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Show the dapp URL reported in `originMetadata` in the "Request from" row of the sign-transaction and sign-message confirmations, marked "Not verified", and hide the row when there is nothing verifiable or self-reported to show (previously a WalletConnect channel id was displayed verbatim as the requesting site)
- Stop sending unverifiable origins to the security alerts API: a WalletConnect channel id or a self-reported dapp URL can flip a Blockaid verdict, so such requests are now reported as wallet-initiated
- **BREAKING** Bump `@metamask/keyring-api` from `^23.7.0` to `^24.1.0` ([#214](https://github.com/MetaMask/internal-snaps/pull/214))
- **BREAKING** Bump `@metamask/keyring-snap-sdk` from `^9.2.1` to `^10.0.0` ([#214](https://github.com/MetaMask/internal-snaps/pull/214))
- **BREAKING** Bump `@metamask/snaps-sdk` from `^11.2.0` to `^12.0.1` ([#214](https://github.com/MetaMask/internal-snaps/pull/214))
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 @@ -25,6 +25,12 @@
"confirmation.origin.tooltip": {
"message": "This is the site asking for your confirmation."
},
"confirmation.origin.unverified": {
"message": "Not verified"
},
"confirmation.origin.unverified.tooltip": {
"message": "This site is reported by the app that sent the request. MetaMask can't verify it."
},
"confirmation.from": {
"message": "From"
},
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 @@ -25,6 +25,12 @@
"confirmation.origin.tooltip": {
"message": "Este es el sitio que solicita su confirmación."
},
"confirmation.origin.unverified": {
"message": "Not verified"
},
"confirmation.origin.unverified.tooltip": {
"message": "This site is reported by the app that sent the request. MetaMask can't verify it."
},
"confirmation.from": {
"message": "De"
},
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 @@ -23,6 +23,12 @@
"confirmation.origin.tooltip": {
"message": "This is the site asking for your confirmation."
},
"confirmation.origin.unverified": {
"message": "Not verified"
},
"confirmation.origin.unverified.tooltip": {
"message": "This site is reported by the app that sent the request. MetaMask can't verify it."
},
"confirmation.from": {
"message": "From"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ function buildMockSignTransactionInterfaceContext(
overrides: Partial<ConfirmSignTransactionContext> = {},
): ConfirmSignTransactionContext {
return {
originMetadata: null,
scope: Network.Mainnet,
account: {
id: 'account-1',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,7 +462,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 @@ -476,7 +476,9 @@ describe('ConfirmationHandler', () => {
mockSnapClient,
mockState,
expect.objectContaining({
origin: 'example.com',
// The raw origin is kept: the view resolves what to display and
// the security scan only accepts a verifiable URL.
origin: 'https://example.com',
transactionRawData: mockTransactionRawData,
}),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,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 @@ -185,7 +184,7 @@ export class ConfirmationHandler {
amount,
fees,
asset,
origin: formatOrigin(origin),
origin,
accountType,
transactionRawData,
},
Expand Down Expand Up @@ -273,6 +272,7 @@ export class ConfirmationHandler {
account,
transaction: { rawDataHex: '', type: '' },
origin: 'MetaMask',
originMetadata: null,
preferences,
networkImage: TRX_IMAGE_SVG,
scan: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,48 @@ describe('TransactionScanService', () => {
timestamp: Date.now(),
});

describe('origin forwarding', () => {
const scanWithOrigin = async (
origin: string,
): Promise<jest.Mocked<SecurityAlertsApiClient>['scanTransaction']> => {
const mockSecurityAlertsApiClient = createMockSecurityAlertsApiClient({
simulation: { status: 'Success', account_summary: {} },
validation: { status: 'Success', result_type: 'Benign' },
} as SecurityAlertSimulationValidationResponse);

const service = new TransactionScanService(
mockSecurityAlertsApiClient as unknown as SecurityAlertsApiClient,
createMockSnapClient() as unknown as SnapClient,
mockLogger,
);

await service.scanTransaction({
accountAddress: 'TExvJsxzPyAZ2NtkrWgNKnbLkpqnFJ73DT',
transactionRawData: createWellFormedTransactionRawData(),
origin,
scope: Network.Mainnet,
options: ['simulation'],
});

return mockSecurityAlertsApiClient.scanTransaction;
};

it.each([
['a WalletConnect channel id', '4f3a1b2c-0000-4000-8000-000000000000'],
['the MetaMask origin', 'MetaMask'],
])('does not forward %s to the scan', async (_, origin) => {
expect(await scanWithOrigin(origin)).toHaveBeenCalledWith(
expect.objectContaining({ origin: 'https://metamask.io' }),
);
});

it('forwards a verifiable origin to the scan', async () => {
expect(await scanWithOrigin('https://tronscan.org')).toHaveBeenCalledWith(
expect.objectContaining({ origin: 'https://tronscan.org' }),
);
});
});

describe('estimated changes decimal precision', () => {
it('computes display value from raw_value and decimals', async () => {
const mockApiResponse: SecurityAlertSimulationValidationResponse = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Logger } from '@metamask/snap-networks-utils';
import { resolveOrigin } from '@metamask/snap-networks-utils';
import { BigNumber } from 'bignumber.js';
import type { Types as TronwebTypes } from 'tronweb';

Expand All @@ -20,7 +21,6 @@ import type {
} from './types';
import { ScanStatus, SecurityAlertResponse, SimulationStatus } from './types';

const METAMASK_ORIGIN = 'metamask';
const METAMASK_ORIGIN_URL = 'https://metamask.io';

export class TransactionScanService {
Expand Down Expand Up @@ -103,7 +103,12 @@ export class TransactionScanService {
const result = await this.#securityAlertsApiClient.scanTransaction({
accountAddress,
transactionRawData,
origin: origin === METAMASK_ORIGIN ? METAMASK_ORIGIN_URL : origin,
// Only a verifiable origin may reach the scan: the URL is a core
// heuristic and can flip a verdict, so an unverifiable one (a
// WalletConnect channel id, or a URL self-reported by the requester)
// would let a dapp influence the check meant to catch it. Those are
// reported as wallet-initiated instead.
origin: resolveOrigin(origin).verifiedOrigin ?? METAMASK_ORIGIN_URL,
options,
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { ComponentOrElement } from '@metamask/snaps-sdk';
import { Box, Icon, Text as SnapText, Tooltip } from '@metamask/snaps-sdk/jsx';

import type { Locale } from '../../../utils/i18n';
import { i18n } from '../../../utils/i18n';

export type OriginRowProps = {
/** Hostname to display. The row is not rendered when `null`. */
displayOrigin: string | null;
/** Whether the hostname was reported by the requester and can't be verified. */
isSelfReported: boolean;
locale: Locale;
};

/**
* The "Request from" row of a confirmation.
*
* A self-reported origin is displayed with an explicit "not verified" marker:
* it comes from the requesting app over a transport that cannot prove it, so
* showing it bare would imply a verification we never made.
*
* @param props - The component props.
* @param props.displayOrigin - Hostname to display, or `null` to render nothing.
* @param props.isSelfReported - Whether the hostname is unverifiable.
* @param props.locale - The locale used for the labels.
* @returns The origin row, or `null` when there is nothing to display.
*/
export const OriginRow = ({
displayOrigin,
isSelfReported,
locale,
}: OriginRowProps): ComponentOrElement | null => {
if (!displayOrigin) {
return null;
}

const translate = i18n(locale);

return (
<Box alignment="space-between" direction="horizontal">
<Box direction="horizontal" alignment="start">
<SnapText fontWeight="medium" color="alternative">
{translate('confirmation.origin')}
</SnapText>
<Tooltip
content={translate(
isSelfReported
? 'confirmation.origin.unverified.tooltip'
: 'confirmation.origin.tooltip',
)}
>
<Icon name="question" color="muted" />
</Tooltip>
</Box>
<Box direction="horizontal" alignment="end">
<SnapText>{displayOrigin}</SnapText>
{isSelfReported ? (
<SnapText color="warning">
{translate('confirmation.origin.unverified')}
</SnapText>
) : null}
</Box>
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { SelfReportedOriginMetadata } from '@metamask/snap-networks-utils';
import { resolveOrigin } from '@metamask/snap-networks-utils';
import type { ComponentOrElement } from '@metamask/snaps-sdk';
import {
Address,
Expand All @@ -6,11 +8,9 @@ import {
Container,
Footer,
Heading,
Icon,
Image,
Section,
Text as SnapText,
Tooltip,
} from '@metamask/snaps-sdk/jsx';

import { Networks } from '../../../../constants';
Expand All @@ -19,6 +19,7 @@ import type { TronKeyringAccount } from '../../../../entities/keyring-account';
import { TRX_IMAGE_SVG } from '../../../../static/tron-logo';
import type { Locale } from '../../../../utils/i18n';
import { i18n } from '../../../../utils/i18n';
import { OriginRow } from '../../components/OriginRow';
import { ConfirmSignMessageFormNames } from './events';

export type ConfirmSignMessageProps = {
Expand All @@ -28,6 +29,7 @@ export type ConfirmSignMessageProps = {
locale: Locale;
networkImage: string | null;
origin: string;
originMetadata: SelfReportedOriginMetadata | null;
};

export const ConfirmSignMessage = ({
Expand All @@ -37,9 +39,14 @@ export const ConfirmSignMessage = ({
locale,
networkImage,
origin,
originMetadata,
}: ConfirmSignMessageProps): ComponentOrElement => {
const translate = i18n(locale);
const { address } = account;
const { displayOrigin, isSelfReported } = resolveOrigin(
origin,
originMetadata,
);
const addressCaip10 = `${scope}:${address}` as
| `0x${string}`
| `${string}:${string}:${string}`;
Expand All @@ -63,19 +70,11 @@ export const ConfirmSignMessage = ({
</Section>

<Section>
{origin ? (
<Box alignment="space-between" direction="horizontal">
<Box direction="horizontal" alignment="start">
<SnapText fontWeight="medium" color="alternative">
{translate('confirmation.origin')}
</SnapText>
<Tooltip content={translate('confirmation.origin.tooltip')}>
<Icon name="question" color="muted" />
</Tooltip>
</Box>
<SnapText>{origin}</SnapText>
</Box>
) : null}
<OriginRow
displayOrigin={displayOrigin}
isSelfReported={isSelfReported}
locale={locale}
/>
<Box alignment="space-between" direction="horizontal">
<SnapText fontWeight="medium" color="alternative">
{translate('confirmation.account')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type { Network } from '../../../../constants';
import snapContext from '../../../../context';
import type { TronKeyringAccount } from '../../../../entities/keyring-account';
import { TRX_IMAGE_SVG } from '../../../../static/tron-logo';
import { formatOrigin } from '../../../../utils/formatOrigin';
import { FALLBACK_LANGUAGE } from '../../../../utils/i18n';
import { SignMessageRequestStruct } from '../../../../validation/structs';
import { ConfirmSignMessage } from './ConfirmSignMessage';
Expand All @@ -30,6 +29,7 @@ export async function render(
},
scope,
origin,
originMetadata,
} = request;

// Decode the base64 message to get the raw message
Expand All @@ -48,7 +48,8 @@ export async function render(
scope={scope as Network}
locale={locale}
networkImage={TRX_IMAGE_SVG}
origin={formatOrigin(origin)}
origin={origin}
originMetadata={originMetadata ?? null}
/>,
{},
);
Expand Down
Loading
Loading