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

## [Unreleased]

### Changed

- Render the confirmation "Estimated changes" section with the shared `EstimatedChanges` component from `@metamask/snap-networks-utils` ([#396](https://github.com/MetaMask/internal-snaps/pull/396))
- Estimated changes from the previous scan now stay visible while a confirmation re-scans, instead of showing a loading skeleton

### Fixed

- Show "Estimated changes are not available" instead of "No estimated changes" when the transaction scan returns an error result, such as for a malformed transaction ([#396](https://github.com/MetaMask/internal-snaps/pull/396))
- 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))
- Show "Estimated changes are not available" instead of "No estimated changes" when the transaction scan returns an error result, such as for a malformed transaction ([#396](https://github.com/MetaMask/internal-snaps/pull/396))

## [4.0.0]

Expand Down
8 changes: 4 additions & 4 deletions packages/tron-wallet-snap/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 72.69,
functions: 79.95,
lines: 85.85,
statements: 85.86,
branches: 72.49,
functions: 79.91,
lines: 85.79,
statements: 85.79,
},
},
};
2 changes: 1 addition & 1 deletion packages/tron-wallet-snap/snap.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"url": "https://github.com/MetaMask/internal-snaps.git"
},
"source": {
"shasum": "L4NZ35MXuCRWHzwx4paNtKwrBWApWCqwQZ0dpp9XeoI=",
"shasum": "1IlkNeNaW28YQoE0wK3OdDJctxoEBDqK6cxLQ0GyifY=",
"location": {
"npm": {
"filePath": "dist/bundle.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { TransactionScanAssetChange } from '../../../../services/transaction-scan/types';
import { FetchStatus } from '../../../../types/snap';
import type { Preferences } from '../../../../types/snap';
import { EstimatedChanges } from './EstimatedChanges';

const preferences = { locale: 'en', currency: 'usd' } as Preferences;

const trxOut: TransactionScanAssetChange = {
type: 'out',
value: '10',
price: null,
symbol: 'TRX',
name: 'Tron',
logo: 'https://example.com/trx.png',
assetType: 'native',
};

const usdtIn: TransactionScanAssetChange = {
...trxOut,
type: 'in',
value: '5',
symbol: 'USDT',
logo: null,
};

const render = (
props: Partial<Parameters<typeof EstimatedChanges>[0]>,
): string =>
JSON.stringify(
EstimatedChanges({
changes: { assets: [trxOut, usdtIn] },
scanStatus: 'SUCCESS',
preferences,
scanFetchStatus: FetchStatus.Fetched,
...props,
}),
);

describe('EstimatedChanges', () => {
it('renders send and receive rows with translated labels', () => {
const serialized = render({});

expect(serialized).toContain('Estimated changes');
expect(serialized).toContain('You send');
expect(serialized).toContain('-10 TRX');
expect(serialized).toContain('You receive');
expect(serialized).toContain('+5 USDT');
expect(serialized).toContain(trxOut.logo);
});

it.each([FetchStatus.Loading, FetchStatus.Fetching])(
'renders a skeleton while %s and nothing is estimated yet',
(scanFetchStatus) => {
expect(render({ changes: null, scanFetchStatus })).toContain(
'"type":"Skeleton"',
);
},
);

it('keeps previously estimated rows visible while re-fetching', () => {
const serialized = render({ scanFetchStatus: FetchStatus.Fetching });

expect(serialized).toContain('-10 TRX');
expect(serialized).not.toContain('"type":"Skeleton"');
});

it('renders not available when the scan fetch fails', () => {
expect(
render({ changes: null, scanFetchStatus: FetchStatus.Error }),
).toContain('Estimated changes are not available');
});

it('renders not available when the scan result is an error', () => {
const serialized = render({
changes: { assets: [] },
scanStatus: 'ERROR',
});

expect(serialized).toContain('Estimated changes are not available');
expect(serialized).not.toContain('No estimated changes');
});

it('keeps estimated rows visible when the scan result is an error', () => {
const serialized = render({ scanStatus: 'ERROR' });

expect(serialized).toContain('-10 TRX');
expect(serialized).not.toContain('Estimated changes are not available');
});

it('renders no changes when fetched without assets', () => {
expect(render({ changes: { assets: [] } })).toContain(
'No estimated changes',
);
});
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import { EstimatedChanges as SharedEstimatedChanges } from '@metamask/snap-networks-utils';
import type { ComponentOrElement } from '@metamask/snaps-sdk';
import {
Box,
Section,
Text as SnapText,
Icon,
Tooltip,
Image,
Skeleton,
} from '@metamask/snaps-sdk/jsx';

import type { TransactionScanEstimatedChanges } from '../../../../services/transaction-scan/types';
import type {
TransactionScanEstimatedChanges,
TransactionScanStatus,
} from '../../../../services/transaction-scan/types';
import { FetchStatus } from '../../../../types/snap';
import type { Preferences } from '../../../../types/snap';
import { formatAmount } from '../../../../utils/formatAmount';
Expand All @@ -18,148 +13,39 @@ import { isFetchStatusLoadingOrFetching } from '../../../../utils/isFetchStatusL

type EstimatedChangesProps = {
changes: TransactionScanEstimatedChanges | null;
scanStatus: TransactionScanStatus | null;
preferences: Preferences;
scanFetchStatus: FetchStatus;
};

const EstimatedChangesSkeleton = ({
preferences,
}: {
preferences: Preferences;
}): ComponentOrElement => {
const translate = i18n(preferences.locale);

return (
<Section direction="vertical">
<Box direction="horizontal" alignment="start">
<SnapText fontWeight="medium">
{translate('confirmation.estimatedChanges.title')}
</SnapText>
<Tooltip content={translate('confirmation.estimatedChanges.tooltip')}>
<Icon name="info" />
</Tooltip>
</Box>
<Box alignment="space-between" direction="horizontal">
<Skeleton width={60} />
<Skeleton width={100} />
</Box>
</Section>
);
};

const EstimatedChangesHeader = ({
preferences,
}: {
preferences: Preferences;
}): ComponentOrElement => {
const translate = i18n(preferences.locale);

return (
<Box direction="horizontal" alignment="start">
<SnapText fontWeight="medium">
{translate('confirmation.estimatedChanges.title')}
</SnapText>
<Tooltip content={translate('confirmation.estimatedChanges.tooltip')}>
<Icon name="info" />
</Tooltip>
</Box>
);
};

type AssetChangeProps = {
asset: TransactionScanEstimatedChanges['assets'][0];
};

const AssetChange = ({ asset }: AssetChangeProps): ComponentOrElement => {
const formattedValue = formatAmount(asset.value);
const isOut = asset.type === 'out';

return (
<Box direction="horizontal" alignment="end">
{asset.logo ? (
<Box alignment="center" center>
<Image src={asset.logo} borderRadius="full" height={16} width={16} />
</Box>
) : null}
<SnapText color={isOut ? 'error' : 'success'}>
{isOut ? '-' : '+'}
{formattedValue} {asset.symbol}
</SnapText>
</Box>
);
};

export const EstimatedChanges = ({
changes,
scanStatus,
preferences,
scanFetchStatus,
}: EstimatedChangesProps): ComponentOrElement => {
const translate = i18n(preferences.locale);

// Keep "refreshing skeleton" for first loading + subsequent refreshes
const isFetching = isFetchStatusLoadingOrFetching(scanFetchStatus);
const isFetched = scanFetchStatus === FetchStatus.Fetched;
const isFetchError = scanFetchStatus === FetchStatus.Error;

if (isFetching) {
return <EstimatedChangesSkeleton preferences={preferences} />;
}

// API fetch error (network failure, etc.) - show "not available"
if (isFetchError) {
return (
<Section direction="vertical">
<EstimatedChangesHeader preferences={preferences} />
<SnapText color="alternative">
{translate('confirmation.estimatedChanges.notAvailable')}
</SnapText>
</Section>
);
}

const send = changes?.assets.filter((asset) => asset.type === 'out') ?? [];
const receive = changes?.assets.filter((asset) => asset.type === 'in') ?? [];

const hasChanges = send.length > 0 || receive.length > 0;

if (isFetched && !hasChanges) {
return (
<Section direction="vertical">
<EstimatedChangesHeader preferences={preferences} />
<SnapText color="alternative">
{translate('confirmation.estimatedChanges.noChanges')}
</SnapText>
</Section>
);
}

return (
<Section>
<EstimatedChangesHeader preferences={preferences} />
{send?.length > 0 ? (
<Box alignment="space-between" direction="horizontal">
<SnapText fontWeight="medium" color="alternative">
{translate('confirmation.estimatedChanges.send')}
</SnapText>
<Box>
{send?.map((asset) => (
<AssetChange asset={asset} />
))}
</Box>
</Box>
) : null}
{receive?.length > 0 ? (
<Box alignment="space-between" direction="horizontal">
<SnapText fontWeight="medium" color="alternative">
{translate('confirmation.estimatedChanges.receive')}
</SnapText>
<Box>
{receive?.map((asset) => (
<AssetChange asset={asset} />
))}
</Box>
</Box>
) : null}
</Section>
<SharedEstimatedChanges
assets={(changes?.assets ?? []).map((asset) => ({
type: asset.type,
value: formatAmount(asset.value),
symbol: asset.symbol,
logo: asset.logo,
}))}
labels={{
title: translate('confirmation.estimatedChanges.title'),
tooltip: translate('confirmation.estimatedChanges.tooltip'),
send: translate('confirmation.estimatedChanges.send'),
receive: translate('confirmation.estimatedChanges.receive'),
notAvailable: translate('confirmation.estimatedChanges.notAvailable'),
noChanges: translate('confirmation.estimatedChanges.noChanges'),
}}
isFetching={isFetchStatusLoadingOrFetching(scanFetchStatus)}
isUnavailable={
scanFetchStatus === FetchStatus.Error || scanStatus === 'ERROR'
}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export const ConfirmSignTransaction = ({
<EstimatedChanges
scanFetchStatus={scanFetchStatus}
changes={scan?.estimatedChanges ?? null}
scanStatus={scan?.status ?? null}
preferences={preferences}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export const ConfirmTransactionRequest = ({
<EstimatedChanges
scanFetchStatus={scanFetchStatus}
changes={scan?.estimatedChanges ?? null}
scanStatus={scan?.status ?? null}
preferences={preferences}
/>
);
Expand Down
Loading