Skip to content

Commit 56c9315

Browse files
committed
chore(runway): cherry-pick fix: validate type of security data in token details and fetch when necessary cp-7.76.0 (#29787)
## **Description** ### Bug When navigating from the Swap/Bridge "Select token" screen to Token Details via the (i) icon, security info (badge, SecurityTrustEntryCard, warning banners) is missing. ### Root Cause The Bridge `/getTokens/popular` API returns security data in a different shape `({ type: "Verified" })` than what Token Details expects `({ resultType: "Verified", features: [...], ... })`. When navigating, the entire token object — including this wrong-shaped securityData — is spread into route params. `useTokenSecurityData` sees it as `truthy` prefetched data, skips its own API call, and the UI reads resultType / features → undefined → nothing renders. ### Fix Added a runtime type guard in `useTokenSecurityData` that validates `prefetchedData` has the required `resultType` (string) and features (array) before trusting it. If the shape is invalid, the hook falls through to `fetchTokenAssets()` and gets the full, correctly-shaped data. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: Fixed security badges and trust info now display correctly on Token Details when navigating from the Swap token selector. ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/1dbc3bbd-293d-4b90-a473-8ce505b8c718 ### **After** <!-- [screenshots/recordings] --> https://github.com/user-attachments/assets/61466686-09a6-45db-8961-7fd5d4690ff8 ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [ ] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [ ] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: adds a small runtime type guard and a focused test to ensure `useTokenSecurityData` falls back to fetching when prefetched security data is malformed. > > **Overview** > Fixes Token Details security UI missing when navigating from Swap/Bridge by **validating `prefetchedData` at runtime** in `useTokenSecurityData` (requires `resultType` string and `features` array) and treating invalid shapes as absent so the hook fetches via `fetchTokenAssets`. > > Adds a regression test covering the Bridge-style wrong-shaped data to ensure the hook ignores it and fetches correct security data instead. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2e9c4c8. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 14e7dee commit 56c9315

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

app/components/UI/TokenDetails/hooks/useTokenSecurityData.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,42 @@ describe('useTokenSecurityData', () => {
109109
expect(result.current.securityData).toBeNull();
110110
});
111111

112+
it('ignores prefetchedData with wrong shape and fetches instead', async () => {
113+
const assetId = 'eip155:1/erc20:0x1234' as CaipAssetType;
114+
mockFetchTokenAssets.mockResolvedValue([
115+
{
116+
assetId,
117+
name: 'Test Token',
118+
symbol: 'TEST',
119+
decimals: 18,
120+
securityData: mockSecurityData,
121+
},
122+
]);
123+
124+
// Bridge SecurityData shape: { type: "Verified" } — missing resultType
125+
const wrongShapedData = {
126+
type: 'Verified',
127+
} as unknown as TokenSecurityData;
128+
129+
const { result } = renderHook(() =>
130+
useTokenSecurityData({
131+
assetId,
132+
prefetchedData: wrongShapedData,
133+
}),
134+
);
135+
136+
expect(result.current.isLoading).toBe(true);
137+
138+
await waitFor(() => {
139+
expect(result.current.isLoading).toBe(false);
140+
});
141+
142+
expect(mockFetchTokenAssets).toHaveBeenCalledWith([assetId], {
143+
includeTokenSecurityData: true,
144+
});
145+
expect(result.current.securityData).toBe(mockSecurityData);
146+
});
147+
112148
it('does not fetch when assetId is null', () => {
113149
const { result } = renderHook(() =>
114150
useTokenSecurityData({ assetId: null }),

app/components/UI/TokenDetails/hooks/useTokenSecurityData.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,20 @@ interface UseTokenSecurityDataResult {
1818
error: Error | null;
1919
}
2020

21+
const isValidTokenSecurityData = (data: unknown): data is TokenSecurityData =>
22+
data != null &&
23+
typeof data === 'object' &&
24+
typeof (data as TokenSecurityData).resultType === 'string' &&
25+
Array.isArray((data as TokenSecurityData).features);
26+
2127
export const useTokenSecurityData = ({
2228
assetId,
23-
prefetchedData,
29+
prefetchedData: rawPrefetchedData,
2430
}: UseTokenSecurityDataOpts): UseTokenSecurityDataResult => {
31+
const prefetchedData = isValidTokenSecurityData(rawPrefetchedData)
32+
? rawPrefetchedData
33+
: undefined;
34+
2535
const [securityData, setSecurityData] = useState<TokenSecurityData | null>(
2636
prefetchedData ?? null,
2737
);

0 commit comments

Comments
 (0)