Skip to content

Commit c3d9fe3

Browse files
chore: filter out Tron staking special assets (#26360)
## **Description** As part of Tron's staking experience improvements we will be sending more special assets from the Snap to the Extension. These special assets are not tradeable tokens and should be filtered out from selectors like we already do for Staked TRX for example. This PR: - Adds the new special assets that should be ignored by the selectors - Renames the variables that deal with this logic to be more inclusive of assets that are not resources (only Energy and Bandwidth are resources) ## **Changelog** CHANGELOG entry: null ## **Related issues** Closes: [NEB-582](https://consensyssoftware.atlassian.net/browse/NEB-582), [NEB-584](https://consensyssoftware.atlassian.net/browse/NEB-584), [NEB-586](https://consensyssoftware.atlassian.net/browse/NEB-586) ## **Manual testing steps** All existing Tron functionality should remain unchanged ## **Screenshots/Recordings** As you can see, the new assets being loaded from the preview build of MetaMask/snap-tron-wallet#226 are not being shown here. ### **Before** n/a ### **After** n/a ## **Pre-merge author checklist** - [x] I've followed MetaMask Contributor Docs and MetaMask Mobile Coding Standards. - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using JSDoc format if applicable - [x] I've applied the right labels on the PR [NEB-582]: https://consensyssoftware.atlassian.net/browse/NEB-582?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [NEB-584]: https://consensyssoftware.atlassian.net/browse/NEB-584?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ [NEB-586]: https://consensyssoftware.atlassian.net/browse/NEB-586?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes token/asset filtering for Tron by excluding additional Snap-provided “special assets” from sorted asset lists and unified multichain token lists, which could inadvertently hide tokens if symbols collide or filtering is misapplied. Scope is contained to Tron selectors/utilities and related UI consumers, with broad test updates. > > **Overview** > Introduces a broader Tron *“special assets”* concept (resources + staking lifecycle assets) and filters these virtual tokens out of user-facing asset/token lists. > > Renames and expands the Tron selector from `selectTronResourcesBySelectedAccountGroup` to `selectTronSpecialAssetsBySelectedAccountGroup` (and `TronResourcesMap` to `TronSpecialAssetsMap`), adding mappings for `trxReadyForWithdrawal`, `trxStakingRewards`, and `trxInLockPeriod` while preserving `totalStakedTrx` computation. > > Centralizes special-asset detection in `core/Multichain/utils` via `isTronSpecialAsset` and reuses it in `selectSortedAssetsBySelectedAccountGroup`, `selectAccountTokensAcrossChainsUnified`, and Bridge `isTradableToken`; updates related Earn/TokenDetails/AssetOverview hooks and tests accordingly. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 893e98a. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 65e5c81 commit c3d9fe3

20 files changed

Lines changed: 444 additions & 194 deletions

File tree

app/components/UI/AssetOverview/TronEnergyBandwidthDetail/TronEnergyBandwidthDetail.test.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import ResourceRing from './ResourceRing';
66
import renderWithProvider from '../../../../util/test/renderWithProvider';
77
import { backgroundState } from '../../../../util/test/initial-root-state';
88
import {
9-
selectTronResourcesBySelectedAccountGroup,
10-
TronResourcesMap,
9+
selectTronSpecialAssetsBySelectedAccountGroup,
10+
TronSpecialAssetsMap,
1111
} from '../../../../selectors/assets/assets-list';
1212

1313
jest.mock('./ResourceRing', () => ({
@@ -33,21 +33,24 @@ jest.mock('../../../../../locales/i18n', () => ({
3333
}));
3434

3535
jest.mock('../../../../selectors/assets/assets-list', () => ({
36-
selectTronResourcesBySelectedAccountGroup: jest.fn(),
36+
selectTronSpecialAssetsBySelectedAccountGroup: jest.fn(),
3737
}));
3838

3939
type SelectorReturn = ReturnType<
40-
typeof selectTronResourcesBySelectedAccountGroup
40+
typeof selectTronSpecialAssetsBySelectedAccountGroup
4141
>;
4242

43-
const createEmptyResourcesMap = (): TronResourcesMap => ({
43+
const createEmptySpecialAssetsMap = (): TronSpecialAssetsMap => ({
4444
energy: undefined,
4545
bandwidth: undefined,
4646
maxEnergy: undefined,
4747
maxBandwidth: undefined,
4848
stakedTrxForEnergy: undefined,
4949
stakedTrxForBandwidth: undefined,
5050
totalStakedTrx: 0,
51+
trxReadyForWithdrawal: undefined,
52+
trxStakingRewards: undefined,
53+
trxInLockPeriod: undefined,
5154
});
5255

5356
interface Resource {
@@ -78,7 +81,7 @@ describe('TronEnergyBandwidthDetail', () => {
7881
});
7982

8083
it('renders values, coverage counts, and passes correct progress to ResourceRing', () => {
81-
jest.mocked(selectTronResourcesBySelectedAccountGroup).mockReturnValue({
84+
jest.mocked(selectTronSpecialAssetsBySelectedAccountGroup).mockReturnValue({
8285
energy: res('energy', 130000),
8386
bandwidth: res('bandwidth', 560),
8487
maxEnergy: res('max-energy', 200000),
@@ -110,7 +113,7 @@ describe('TronEnergyBandwidthDetail', () => {
110113
});
111114

112115
it('parses balances and caps progress', () => {
113-
jest.mocked(selectTronResourcesBySelectedAccountGroup).mockReturnValue({
116+
jest.mocked(selectTronSpecialAssetsBySelectedAccountGroup).mockReturnValue({
114117
energy: res('energy', '1000'),
115118
bandwidth: res('bandwidth', '2000'),
116119
maxEnergy: res('max-energy', '400'),
@@ -136,8 +139,8 @@ describe('TronEnergyBandwidthDetail', () => {
136139

137140
it('handles missing resources by showing zeros and 0 progress', () => {
138141
jest
139-
.mocked(selectTronResourcesBySelectedAccountGroup)
140-
.mockReturnValue(createEmptyResourcesMap());
142+
.mocked(selectTronSpecialAssetsBySelectedAccountGroup)
143+
.mockReturnValue(createEmptySpecialAssetsMap());
141144

142145
const { getAllByText, getByText } = renderWithProvider(
143146
<TronEnergyBandwidthDetail />,

app/components/UI/AssetOverview/TronEnergyBandwidthDetail/useTronResources.test.ts

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { useSelector } from 'react-redux';
44

55
import { useTronResources } from './useTronResources';
66
import {
7-
selectTronResourcesBySelectedAccountGroup,
8-
TronResourcesMap,
7+
selectTronSpecialAssetsBySelectedAccountGroup,
8+
TronSpecialAssetsMap,
99
} from '../../../../selectors/assets/assets-list';
1010

1111
jest.mock('react-redux', () => ({
@@ -15,28 +15,31 @@ jest.mock('react-redux', () => ({
1515
jest.mock('../../../../selectors/assets/assets-list', () => ({
1616
__esModule: true,
1717
...jest.requireActual('../../../../selectors/assets/assets-list'),
18-
selectTronResourcesBySelectedAccountGroup: jest.fn(),
18+
selectTronSpecialAssetsBySelectedAccountGroup: jest.fn(),
1919
}));
2020

2121
const mockUseSelector = useSelector as jest.MockedFunction<typeof useSelector>;
22-
const mockSelectTronResourcesBySelectedAccountGroup =
23-
selectTronResourcesBySelectedAccountGroup as jest.MockedFunction<
24-
typeof selectTronResourcesBySelectedAccountGroup
22+
const mockSelectTronSpecialAssetsBySelectedAccountGroup =
23+
selectTronSpecialAssetsBySelectedAccountGroup as jest.MockedFunction<
24+
typeof selectTronSpecialAssetsBySelectedAccountGroup
2525
>;
2626

2727
interface MockTronAsset {
2828
symbol?: string;
2929
balance?: string | number;
3030
}
3131

32-
const createEmptyResourcesMap = (): TronResourcesMap => ({
32+
const createEmptySpecialAssetsMap = (): TronSpecialAssetsMap => ({
3333
energy: undefined,
3434
bandwidth: undefined,
3535
maxEnergy: undefined,
3636
maxBandwidth: undefined,
3737
stakedTrxForEnergy: undefined,
3838
stakedTrxForBandwidth: undefined,
3939
totalStakedTrx: 0,
40+
trxReadyForWithdrawal: undefined,
41+
trxStakingRewards: undefined,
42+
trxInLockPeriod: undefined,
4043
});
4144

4245
const createTronAsset = (
@@ -52,24 +55,27 @@ describe('useTronResources', () => {
5255
jest.clearAllMocks();
5356

5457
mockUseSelector.mockImplementation((selector: any) => selector());
55-
mockSelectTronResourcesBySelectedAccountGroup.mockReturnValue(
56-
createEmptyResourcesMap(),
58+
mockSelectTronSpecialAssetsBySelectedAccountGroup.mockReturnValue(
59+
createEmptySpecialAssetsMap(),
5760
);
5861
});
5962

6063
it('builds energy and bandwidth resources from base max capacity', () => {
61-
const tronResourcesMap: TronResourcesMap = {
64+
const tronSpecialAssetsMap: TronSpecialAssetsMap = {
6265
energy: createTronAsset('energy', '500') as any,
6366
bandwidth: createTronAsset('bandwidth', '300') as any,
6467
maxEnergy: createTronAsset('max-energy', '1000') as any,
6568
maxBandwidth: createTronAsset('max-bandwidth', '600') as any,
6669
stakedTrxForEnergy: createTronAsset('strx-energy', '500') as any,
6770
stakedTrxForBandwidth: createTronAsset('strx-bandwidth', 0) as any,
6871
totalStakedTrx: 500,
72+
trxReadyForWithdrawal: undefined,
73+
trxStakingRewards: undefined,
74+
trxInLockPeriod: undefined,
6975
};
7076

71-
mockSelectTronResourcesBySelectedAccountGroup.mockReturnValue(
72-
tronResourcesMap,
77+
mockSelectTronSpecialAssetsBySelectedAccountGroup.mockReturnValue(
78+
tronSpecialAssetsMap,
7379
);
7480

7581
const { result } = renderHook(() => useTronResources());
@@ -84,8 +90,8 @@ describe('useTronResources', () => {
8490
});
8591

8692
it('returns zeroed resources when no Tron resources exist', () => {
87-
mockSelectTronResourcesBySelectedAccountGroup.mockReturnValue(
88-
createEmptyResourcesMap(),
93+
mockSelectTronSpecialAssetsBySelectedAccountGroup.mockReturnValue(
94+
createEmptySpecialAssetsMap(),
8995
);
9096

9197
const { result } = renderHook(() => useTronResources());
@@ -106,14 +112,14 @@ describe('useTronResources', () => {
106112
});
107113

108114
it('parses balances with comma separators', () => {
109-
const tronResourcesMap: TronResourcesMap = {
110-
...createEmptyResourcesMap(),
115+
const tronSpecialAssetsMap: TronSpecialAssetsMap = {
116+
...createEmptySpecialAssetsMap(),
111117
energy: createTronAsset('energy', '1,000') as any,
112118
maxEnergy: createTronAsset('max-energy', '2,000') as any,
113119
};
114120

115-
mockSelectTronResourcesBySelectedAccountGroup.mockReturnValue(
116-
tronResourcesMap,
121+
mockSelectTronSpecialAssetsBySelectedAccountGroup.mockReturnValue(
122+
tronSpecialAssetsMap,
117123
);
118124

119125
const { result } = renderHook(() => useTronResources());
@@ -124,14 +130,14 @@ describe('useTronResources', () => {
124130
});
125131

126132
it('caps percentage at one hundred when current exceeds max', () => {
127-
const tronResourcesMap: TronResourcesMap = {
128-
...createEmptyResourcesMap(),
133+
const tronSpecialAssetsMap: TronSpecialAssetsMap = {
134+
...createEmptySpecialAssetsMap(),
129135
energy: createTronAsset('energy', 200) as any,
130136
maxEnergy: createTronAsset('max-energy', 100) as any,
131137
};
132138

133-
mockSelectTronResourcesBySelectedAccountGroup.mockReturnValue(
134-
tronResourcesMap,
139+
mockSelectTronSpecialAssetsBySelectedAccountGroup.mockReturnValue(
140+
tronSpecialAssetsMap,
135141
);
136142

137143
const { result } = renderHook(() => useTronResources());
@@ -141,14 +147,14 @@ describe('useTronResources', () => {
141147
});
142148

143149
it('sets percentage to zero when balances cannot be parsed', () => {
144-
const tronResourcesMap: TronResourcesMap = {
145-
...createEmptyResourcesMap(),
150+
const tronSpecialAssetsMap: TronSpecialAssetsMap = {
151+
...createEmptySpecialAssetsMap(),
146152
energy: createTronAsset('energy', 'invalid') as any,
147153
maxEnergy: createTronAsset('max-energy', '1000') as any,
148154
};
149155

150-
mockSelectTronResourcesBySelectedAccountGroup.mockReturnValue(
151-
tronResourcesMap,
156+
mockSelectTronSpecialAssetsBySelectedAccountGroup.mockReturnValue(
157+
tronSpecialAssetsMap,
152158
);
153159

154160
const { result } = renderHook(() => useTronResources());

app/components/UI/AssetOverview/TronEnergyBandwidthDetail/useTronResources.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
22
import { useSelector } from 'react-redux';
33
import BigNumber from 'bignumber.js';
44

5-
import { selectTronResourcesBySelectedAccountGroup } from '../../../../selectors/assets/assets-list';
5+
import { selectTronSpecialAssetsBySelectedAccountGroup } from '../../../../selectors/assets/assets-list';
66
import { safeParseBigNumber } from '../../../../util/number/bignumber';
77

88
export interface TronResource {
@@ -49,7 +49,7 @@ export const useTronResources = (): {
4949
bandwidth: TronResource;
5050
} => {
5151
const { energy, bandwidth, maxEnergy, maxBandwidth } = useSelector(
52-
selectTronResourcesBySelectedAccountGroup,
52+
selectTronSpecialAssetsBySelectedAccountGroup,
5353
);
5454

5555
return useMemo(() => {

app/components/UI/Bridge/utils/isTradableToken/index.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,5 +238,38 @@ describe('isTradableToken', () => {
238238

239239
expect(result).toBe(false);
240240
});
241+
242+
it('returns false for Tron Ready for Withdrawal token', () => {
243+
const token = createTestToken({
244+
chainId: TrxScope.Mainnet,
245+
symbol: 'TRX-READY-FOR-WITHDRAWAL',
246+
});
247+
248+
const result = isTradableToken(token);
249+
250+
expect(result).toBe(false);
251+
});
252+
253+
it('returns false for Tron Staking Rewards token', () => {
254+
const token = createTestToken({
255+
chainId: TrxScope.Mainnet,
256+
symbol: 'TRX-STAKING-REWARDS',
257+
});
258+
259+
const result = isTradableToken(token);
260+
261+
expect(result).toBe(false);
262+
});
263+
264+
it('returns false for Tron In Lock Period token', () => {
265+
const token = createTestToken({
266+
chainId: TrxScope.Mainnet,
267+
symbol: 'TRX-IN-LOCK-PERIOD',
268+
});
269+
270+
const result = isTradableToken(token);
271+
272+
expect(result).toBe(false);
273+
});
241274
});
242275
});
Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
import { TrxScope } from '@metamask/keyring-api';
22
import { BridgeToken } from '../../types';
3-
import {
4-
TRON_RESOURCE_SYMBOLS,
5-
TronResourceSymbol,
6-
} from '../../../../../core/Multichain/constants';
3+
import { isTronSpecialAsset } from '../../../../../core/Multichain/utils';
74
import { TokenI } from '../../../Tokens/types';
85

9-
export const isTradableToken = (token: BridgeToken | TokenI) => {
10-
if (token.chainId === TrxScope.Mainnet) {
11-
return !TRON_RESOURCE_SYMBOLS.includes(
12-
token.symbol?.toLowerCase() as TronResourceSymbol,
13-
);
14-
}
15-
return true;
16-
};
6+
export const isTradableToken = (token: BridgeToken | TokenI) =>
7+
token.chainId !== TrxScope.Mainnet ||
8+
!isTronSpecialAsset(token.chainId, token.symbol);

app/components/UI/Earn/components/EarnBalance/EarnBalance.test.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import StakingBalance from '../../../Stake/components/StakingBalance/StakingBala
55
import { TokenI } from '../../../Tokens/types';
66
import EarnLendingBalance from '../EarnLendingBalance';
77
import { selectTrxStakingEnabled } from '../../../../../selectors/featureFlagController/trxStakingEnabled';
8-
import { selectTronResourcesBySelectedAccountGroup } from '../../../../../selectors/assets/assets-list';
8+
import { selectTronSpecialAssetsBySelectedAccountGroup } from '../../../../../selectors/assets/assets-list';
99
import TronStakingButtons from '../Tron/TronStakingButtons';
1010
import { selectIsMusdConversionFlowEnabledFlag } from '../../selectors/featureFlags';
1111

@@ -27,7 +27,7 @@ jest.mock(
2727

2828
jest.mock('../../../../../selectors/assets/assets-list', () => ({
2929
...jest.requireActual('../../../../../selectors/assets/assets-list'),
30-
selectTronResourcesBySelectedAccountGroup: jest.fn(),
30+
selectTronSpecialAssetsBySelectedAccountGroup: jest.fn(),
3131
}));
3232

3333
jest.mock('../Tron/TronStakingButtons', () => ({
@@ -137,23 +137,26 @@ jest.mock('../../hooks/useTronStakeApy', () => ({
137137
}),
138138
}));
139139

140-
const createEmptyResourcesMap = () => ({
140+
const createEmptySpecialAssetsMap = () => ({
141141
energy: undefined,
142142
bandwidth: undefined,
143143
maxEnergy: undefined,
144144
maxBandwidth: undefined,
145145
stakedTrxForEnergy: undefined,
146146
stakedTrxForBandwidth: undefined,
147147
totalStakedTrx: 0,
148+
trxReadyForWithdrawal: undefined,
149+
trxStakingRewards: undefined,
150+
trxInLockPeriod: undefined,
148151
});
149152

150153
describe('EarnBalance', () => {
151154
beforeEach(() => {
152155
jest.clearAllMocks();
153156
(jest.mocked(selectTrxStakingEnabled) as jest.Mock).mockReturnValue(false);
154157
(
155-
jest.mocked(selectTronResourcesBySelectedAccountGroup) as jest.Mock
156-
).mockReturnValue(createEmptyResourcesMap());
158+
jest.mocked(selectTronSpecialAssetsBySelectedAccountGroup) as jest.Mock
159+
).mockReturnValue(createEmptySpecialAssetsMap());
157160
});
158161

159162
describe('Ethereum Mainnet', () => {
@@ -251,7 +254,7 @@ describe('EarnBalance', () => {
251254
describe('TRON', () => {
252255
const mockFlag = selectTrxStakingEnabled as unknown as jest.Mock;
253256
const mockTronResources =
254-
selectTronResourcesBySelectedAccountGroup as unknown as jest.Mock;
257+
selectTronSpecialAssetsBySelectedAccountGroup as unknown as jest.Mock;
255258

256259
it('renders TRON stake button with aprText for TRX without staked positions', () => {
257260
const trx: Partial<TokenI> = {
@@ -261,7 +264,7 @@ describe('EarnBalance', () => {
261264
};
262265

263266
mockFlag.mockReturnValue(true);
264-
mockTronResources.mockReturnValue(createEmptyResourcesMap());
267+
mockTronResources.mockReturnValue(createEmptySpecialAssetsMap());
265268

266269
renderWithProvider(<EarnBalance asset={trx as TokenI} />);
267270

@@ -283,7 +286,7 @@ describe('EarnBalance', () => {
283286

284287
mockFlag.mockReturnValue(true);
285288
mockTronResources.mockReturnValue({
286-
...createEmptyResourcesMap(),
289+
...createEmptySpecialAssetsMap(),
287290
stakedTrxForEnergy: { symbol: 'strx-energy', balance: '1' },
288291
stakedTrxForBandwidth: { symbol: 'strx-bandwidth', balance: '2' },
289292
totalStakedTrx: 3,

app/components/UI/Earn/components/EarnBalance/index.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import EarnLendingBalance from '../EarnLendingBalance';
88
import { selectIsStakeableToken } from '../../../Stake/selectors/stakeableTokens';
99
///: BEGIN:ONLY_INCLUDE_IF(tron)
1010
import TronStakingButtons from '../Tron/TronStakingButtons';
11-
import { selectTronResourcesBySelectedAccountGroup } from '../../../../../selectors/assets/assets-list';
11+
import { selectTronSpecialAssetsBySelectedAccountGroup } from '../../../../../selectors/assets/assets-list';
1212
import { selectTrxStakingEnabled } from '../../../../../selectors/featureFlagController/trxStakingEnabled';
1313
import { hasStakedTrxPositions as hasStakedTrxPositionsUtil } from '../../utils/tron';
1414
import useTronStakeApy from '../../hooks/useTronStakeApy';
@@ -47,10 +47,12 @@ const EarnBalance = ({ asset }: EarnBalanceProps) => {
4747
const isStakedTrxAsset =
4848
isTron && (asset?.ticker === 'sTRX' || asset?.symbol === 'sTRX');
4949

50-
const tronResources = useSelector(selectTronResourcesBySelectedAccountGroup);
50+
const tronSpecialAssets = useSelector(
51+
selectTronSpecialAssetsBySelectedAccountGroup,
52+
);
5153
const hasStakedTrxPositions = React.useMemo(
52-
() => hasStakedTrxPositionsUtil(tronResources),
53-
[tronResources],
54+
() => hasStakedTrxPositionsUtil(tronSpecialAssets),
55+
[tronSpecialAssets],
5456
);
5557

5658
const { apyPercent: tronApyPercent } = useTronStakeApy();

0 commit comments

Comments
 (0)