Skip to content

Commit b04526d

Browse files
authored
fix(Rewards): Ondo campaign text issues cp-7.76.0 (#29815)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** This cleans up some unintentional text issues when displaying Ondo assets in the Rewards tab. This makes the Rewards tab match how Ondo assets are displayed elsewhere in the app. ## **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: null ## **Related issues** Fixes: n/a ## **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] --> ### **After** <img width="1179" height="2556" alt="Simulator Screenshot - E2E Test - 2026-05-06 at 15 10 33" src="https://github.com/user-attachments/assets/6b266c71-e1db-4e72-a30c-d8bcaab36fee" /> <!-- [screenshots/recordings] --> ## **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. --> - [x] 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). - [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](https://jsdoc.app/) format if applicable - [x] 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) - [x] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [x] 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 - [x] 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`. --> - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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 UI/text-only changes in Rewards Ondo views plus removal of a now-unused formatting helper; primary risk is minor regressions in how token names/units are displayed and in i18n key usage. > > **Overview** > Rewards Ondo screens now **preserve backend-provided token display names** (including the `(Ondo Tokenized)` suffix) instead of stripping/truncating branding via `sanitizeOndoTokenName`, and navigation to swaps passes the same unmodified name. > > Ondo portfolio rows adjust layout to truncate long names and change the units subtext from a localized `"{{units}} shares"` string to a direct `${units} ${TOKEN_SYMBOL}` format (uppercased), removing the `position_units` i18n key and associated tests. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 60550e0. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 88b007a commit b04526d

23 files changed

Lines changed: 48 additions & 201 deletions

app/components/UI/Rewards/Views/OndoCampaignRwaSelectorView.test.tsx

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -608,18 +608,8 @@ describe('OndoCampaignRwaSelectorView', () => {
608608
});
609609
});
610610

611-
describe('token name sanitization', () => {
612-
it('strips "Ondo Tokenized " prefix from token names in list rows', () => {
613-
const token = { ...buildToken('AAPL'), name: 'Ondo Tokenized Apple' };
614-
mockUseRwaTokens.mockReturnValue({ data: [token], isLoading: false });
615-
const { getByText, queryByText } = render(
616-
<OndoCampaignRwaSelectorView />,
617-
);
618-
expect(getByText('Apple')).toBeDefined();
619-
expect(queryByText('Ondo Tokenized Apple')).toBeNull();
620-
});
621-
622-
it('strips "(Ondo Tokenized)" suffix from token names in list rows', () => {
611+
describe('Ondo token name display', () => {
612+
it('preserves backend-provided suffix names in list rows', () => {
623613
const token = {
624614
...buildToken('AAPL'),
625615
name: 'Apple (Ondo Tokenized)',
@@ -628,25 +618,21 @@ describe('OndoCampaignRwaSelectorView', () => {
628618
const { getByText, queryByText } = render(
629619
<OndoCampaignRwaSelectorView />,
630620
);
631-
expect(getByText('Apple')).toBeDefined();
632-
expect(queryByText('Apple (Ondo Tokenized)')).toBeNull();
633-
});
634-
635-
it('leaves unrelated token names unchanged', () => {
636-
const token = { ...buildToken('USDY'), name: 'Ondo USD Yield' };
637-
mockUseRwaTokens.mockReturnValue({ data: [token], isLoading: false });
638-
const { getByText } = render(<OndoCampaignRwaSelectorView />);
639-
expect(getByText('Ondo USD Yield')).toBeDefined();
621+
expect(getByText('Apple (Ondo Tokenized)')).toBeDefined();
622+
expect(queryByText('Apple')).toBeNull();
640623
});
641624

642-
it('passes original unsanitized name to goToSwaps when token has Ondo prefix', () => {
643-
const token = { ...buildToken('AAPL'), name: 'Ondo Tokenized Apple' };
625+
it('passes the backend-provided name to goToSwaps', () => {
626+
const token = {
627+
...buildToken('AAPL'),
628+
name: 'Apple (Ondo Tokenized)',
629+
};
644630
mockUseRwaTokens.mockReturnValue({ data: [token], isLoading: false });
645631
const { getByTestId } = render(<OndoCampaignRwaSelectorView />);
646632
fireEvent.press(getByTestId('token-row-AAPL'));
647633
expect(mockGoToSwaps).toHaveBeenCalledWith(
648634
undefined,
649-
expect.objectContaining({ name: 'Ondo Tokenized Apple' }),
635+
expect.objectContaining({ name: 'Apple (Ondo Tokenized)' }),
650636
);
651637
});
652638
});

app/components/UI/Rewards/Views/OndoCampaignRwaSelectorView.tsx

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,7 @@ import ErrorBoundary from '../../../Views/ErrorBoundary';
3636
import { useRwaTokens } from '../../Trending/hooks/useRwaTokens/useRwaTokens';
3737
import TrendingTokenRowItem from '../../Trending/components/TrendingTokenRowItem/TrendingTokenRowItem';
3838
import { getTrendingTokenImageUrl } from '../../Trending/utils/getTrendingTokenImageUrl';
39-
import {
40-
parseCaip19,
41-
caipChainIdToHex,
42-
sanitizeOndoTokenName,
43-
} from '../utils/formatUtils';
39+
import { parseCaip19, caipChainIdToHex } from '../utils/formatUtils';
4440
import { RWA_NETWORKS_LIST } from '../../Trending/utils/trendingNetworksList';
4541
import {
4642
useSwapBridgeNavigation,
@@ -262,30 +258,27 @@ const OndoCampaignRwaSelectorView: React.FC = () => {
262258
sourceToken: srcBridgeToken,
263259
});
264260

265-
// Deduplicate by assetId and sanitize display names.
261+
// Deduplicate by assetId while preserving backend-provided display names.
266262
// Use CAIP-19 assetId (not symbol) for deduplication — symbol comparison
267263
// is fragile when casing differs between chains.
268264
const tokens = useMemo((): TrendingAsset[] => {
269265
const seen = new Set<string>();
270-
return rwaTokens
271-
.filter((token) => {
272-
if (srcTokenAsset && token.assetId === srcTokenAsset) return false;
273-
if (seen.has(token.assetId)) return false;
274-
seen.add(token.assetId);
275-
return true;
276-
})
277-
.map((token) => ({ ...token, name: sanitizeOndoTokenName(token.name) }));
266+
return rwaTokens.filter((token) => {
267+
if (srcTokenAsset && token.assetId === srcTokenAsset) return false;
268+
if (seen.has(token.assetId)) return false;
269+
seen.add(token.assetId);
270+
return true;
271+
});
278272
}, [rwaTokens, srcTokenAsset]);
279273

280274
const handleAssetSelect = useCallback(
281275
(asset: TrendingAsset) => {
282276
const parsed = parseCaip19(asset.assetId);
283277
if (!parsed) return;
284-
const rawToken = rwaTokens.find((t) => t.assetId === asset.assetId);
285278
const destToken: BridgeToken = {
286279
address: parsed.assetReference,
287280
symbol: asset.symbol,
288-
name: rawToken?.name ?? asset.name,
281+
name: asset.name,
289282
decimals: asset.decimals,
290283
chainId: `${parsed.namespace}:${parsed.chainId}` as CaipChainId,
291284
image: getTrendingTokenImageUrl(asset.assetId),
@@ -318,7 +311,6 @@ const OndoCampaignRwaSelectorView: React.FC = () => {
318311
trackEvent,
319312
createEventBuilder,
320313
ondoUsdSrcToken,
321-
rwaTokens,
322314
],
323315
);
324316

app/components/UI/Rewards/components/Campaigns/OndoPortfolio.test.tsx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ jest.mock('../../../../../../locales/i18n', () => ({
142142
'Please try again',
143143
'rewards.ondo_campaign_portfolio.retry': 'Retry',
144144
'rewards.ondo_campaign_portfolio.updated_at': `Updated: ${params?.time ?? ''}`,
145-
'rewards.ondo_campaign_portfolio.position_units': `${params?.units ?? ''} units`,
146145
};
147146
return translations[key] ?? key;
148147
},
@@ -217,10 +216,11 @@ jest.mock(
217216
);
218217

219218
const mockRefetch = jest.fn();
219+
const MOCK_POSITION_DISPLAY_NAME = 'Apple Inc. (Ondo Tokenized)';
220220

221221
const MOCK_POSITION: OndoGmPortfolioPositionDto = {
222222
tokenSymbol: 'AAPLon',
223-
tokenName: 'Apple Inc.',
223+
tokenName: MOCK_POSITION_DISPLAY_NAME,
224224
tokenAsset: 'eip155:1/erc20:0x14c3abf95cb9c93a8b82c1cdcb76d72cb87b2d4c',
225225
units: '45.2',
226226
bookPrice: '200.000000',
@@ -369,7 +369,7 @@ describe('OndoPortfolio', () => {
369369
it('renders the token name', () => {
370370
const { getByText } = render(<OndoPortfolio {...loadedProps} />);
371371

372-
expect(getByText('Apple Inc.')).toBeDefined();
372+
expect(getByText(MOCK_POSITION_DISPLAY_NAME)).toBeDefined();
373373
});
374374
});
375375

@@ -395,8 +395,8 @@ describe('OndoPortfolio', () => {
395395

396396
it('pressing a position row does not throw', () => {
397397
const { getByText } = render(<OndoPortfolio {...loadedProps} />);
398-
fireEvent.press(getByText('Apple Inc.'));
399-
expect(getByText('Apple Inc.')).toBeDefined();
398+
fireEvent.press(getByText(MOCK_POSITION_DISPLAY_NAME));
399+
expect(getByText(MOCK_POSITION_DISPLAY_NAME)).toBeDefined();
400400
});
401401

402402
it('renders empty banner when portfolio has no positions', () => {
@@ -480,7 +480,7 @@ describe('OndoPortfolio', () => {
480480
const props = buildPropsWithBalance(rawHexBalance);
481481
const { getByText } = render(<OndoPortfolio {...props} />);
482482

483-
fireEvent.press(getByText('Apple Inc.'));
483+
fireEvent.press(getByText(MOCK_POSITION_DISPLAY_NAME));
484484

485485
expect(props.onOpenAccountPicker).not.toHaveBeenCalled();
486486
},
@@ -493,7 +493,9 @@ describe('OndoPortfolio', () => {
493493
// empty and the component navigates directly (length === 0 branch).
494494
// Picker not opened either way — we just confirm no throw.
495495
const { getByText } = render(<OndoPortfolio {...props} />);
496-
expect(() => fireEvent.press(getByText('Apple Inc.'))).not.toThrow();
496+
expect(() =>
497+
fireEvent.press(getByText(MOCK_POSITION_DISPLAY_NAME)),
498+
).not.toThrow();
497499
});
498500
});
499501

@@ -503,9 +505,9 @@ describe('OndoPortfolio', () => {
503505
portfolio: MOCK_PORTFOLIO,
504506
};
505507

506-
it('renders the units text', () => {
508+
it('renders units with the uppercased ticker', () => {
507509
const { getByText } = render(<OndoPortfolio {...loadedProps} />);
508-
expect(getByText('45.2 units')).toBeDefined();
510+
expect(getByText('45.2 AAPLON')).toBeDefined();
509511
});
510512

511513
it('renders positive PnL percent in green', () => {
@@ -572,7 +574,7 @@ describe('OndoPortfolio', () => {
572574
onOpenAccountPicker={onOpenAccountPicker}
573575
/>,
574576
);
575-
fireEvent.press(getByText('Apple Inc.'));
577+
fireEvent.press(getByText(MOCK_POSITION_DISPLAY_NAME));
576578
expect(onOpenAccountPicker).not.toHaveBeenCalled();
577579
});
578580
});
@@ -673,7 +675,7 @@ describe('OndoPortfolio', () => {
673675
/>,
674676
);
675677

676-
fireEvent.press(getByText('Apple Inc.'));
678+
fireEvent.press(getByText(MOCK_POSITION_DISPLAY_NAME));
677679

678680
expect(onOpenAccountPicker).toHaveBeenCalledTimes(1);
679681
const config = (onOpenAccountPicker as jest.Mock).mock.calls[0][0];
@@ -726,7 +728,7 @@ describe('OndoPortfolio', () => {
726728
/>,
727729
);
728730

729-
fireEvent.press(getByText('Apple Inc.'));
731+
fireEvent.press(getByText(MOCK_POSITION_DISPLAY_NAME));
730732

731733
expect(onOpenAccountPicker).not.toHaveBeenCalled();
732734
});
@@ -788,7 +790,7 @@ describe('OndoPortfolio', () => {
788790
/>,
789791
);
790792

791-
fireEvent.press(getByText('Apple Inc.'));
793+
fireEvent.press(getByText(MOCK_POSITION_DISPLAY_NAME));
792794

793795
// Account must be found despite key case mismatch → picker opened
794796
expect(onOpenAccountPicker).toHaveBeenCalledTimes(1);
@@ -850,7 +852,7 @@ describe('OndoPortfolio', () => {
850852
/>,
851853
);
852854

853-
fireEvent.press(getByText('Apple Inc.'));
855+
fireEvent.press(getByText(MOCK_POSITION_DISPLAY_NAME));
854856

855857
expect(onOpenAccountPicker).toHaveBeenCalledTimes(1);
856858
const config = (onOpenAccountPicker as jest.Mock).mock.calls[0][0];
@@ -908,7 +910,7 @@ describe('OndoPortfolio', () => {
908910
/>,
909911
);
910912

911-
fireEvent.press(getByText('Apple Inc.'));
913+
fireEvent.press(getByText(MOCK_POSITION_DISPLAY_NAME));
912914

913915
// No subscribed account has balance → navigate directly, picker not opened
914916
expect(onOpenAccountPicker).not.toHaveBeenCalled();

app/components/UI/Rewards/components/Campaigns/OndoPortfolio.tsx

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import {
4141
groupPortfolioPositionsByAsset,
4242
formatPnlPercent,
4343
isPnlNonNegative,
44-
sanitizeOndoTokenName,
4544
} from './OndoPortfolio.utils';
4645
import { selectAllTokenBalances } from '../../../../../selectors/tokenBalancesController';
4746
import { selectAllTokens } from '../../../../../selectors/tokensController';
@@ -458,15 +457,20 @@ const OndoPortfolio: React.FC<OndoPortfolioProps> = ({
458457
justifyContent={BoxJustifyContent.Between}
459458
alignItems={BoxAlignItems.Center}
460459
>
460+
<Box twClassName="flex-1 min-w-0 pr-2">
461+
<Text
462+
variant={TextVariant.BodyMd}
463+
fontWeight={FontWeight.Medium}
464+
numberOfLines={1}
465+
ellipsizeMode="tail"
466+
>
467+
{row.tokenName}
468+
</Text>
469+
</Box>
461470
<Text
462471
variant={TextVariant.BodyMd}
463472
fontWeight={FontWeight.Medium}
464-
>
465-
{sanitizeOndoTokenName(row.tokenName)}
466-
</Text>
467-
<Text
468-
variant={TextVariant.BodyMd}
469-
fontWeight={FontWeight.Medium}
473+
numberOfLines={1}
470474
>
471475
{formatUsd(row.currentValue)}
472476
</Text>
@@ -480,12 +484,7 @@ const OndoPortfolio: React.FC<OndoPortfolioProps> = ({
480484
variant={TextVariant.BodySm}
481485
color={TextColor.TextAlternative}
482486
>
483-
{strings(
484-
'rewards.ondo_campaign_portfolio.position_units',
485-
{
486-
units: row.units,
487-
},
488-
)}
487+
{`${row.units} ${row.tokenSymbol.toUpperCase()}`}
489488
</Text>
490489
{rowPnlPercent ? (
491490
<Text variant={TextVariant.BodySm} color={rowPnlColor}>

app/components/UI/Rewards/components/Campaigns/OndoPortfolio.utils.test.ts

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
groupPortfolioPositionsByAsset,
33
formatPnlPercent,
44
isPnlNonNegative,
5-
sanitizeOndoTokenName,
65
} from './OndoPortfolio.utils';
76

87
describe('groupPortfolioPositionsByAsset', () => {
@@ -112,45 +111,3 @@ describe('isPnlNonNegative', () => {
112111
expect(isPnlNonNegative('—')).toBe(false);
113112
});
114113
});
115-
116-
describe('sanitizeOndoTokenName', () => {
117-
it('strips "(Ondo Tokenized)" suffix and trims', () => {
118-
expect(sanitizeOndoTokenName('US Dollar (Ondo Tokenized)')).toBe(
119-
'US Dollar',
120-
);
121-
});
122-
123-
it('strips "Ondo Tokenized " prefix (trending token API format)', () => {
124-
expect(sanitizeOndoTokenName('Ondo Tokenized Apple')).toBe('Apple');
125-
});
126-
127-
it('is case-insensitive', () => {
128-
expect(sanitizeOndoTokenName('Token (ondo tokenized)')).toBe('Token');
129-
});
130-
131-
it('truncates to 28 characters with ellipsis', () => {
132-
expect(sanitizeOndoTokenName('A Very Long Token Name That Exceeds')).toBe(
133-
'A Very Long Token Name That...',
134-
);
135-
});
136-
137-
it('strips then truncates with ellipsis', () => {
138-
const long = 'Extremely Long Name Here That Keeps Going (Ondo Tokenized)';
139-
const result = sanitizeOndoTokenName(long);
140-
expect(result).toBe('Extremely Long Name Here Tha...');
141-
});
142-
143-
it('does not add ellipsis when exactly 28 characters', () => {
144-
expect(sanitizeOndoTokenName('1234567890123456789012345678')).toBe(
145-
'1234567890123456789012345678',
146-
);
147-
});
148-
149-
it('returns the name unchanged when no stripping or truncation is needed', () => {
150-
expect(sanitizeOndoTokenName('OUSG')).toBe('OUSG');
151-
});
152-
153-
it('handles empty string', () => {
154-
expect(sanitizeOndoTokenName('')).toBe('');
155-
});
156-
});

app/components/UI/Rewards/components/Campaigns/OndoPortfolio.utils.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export {
88
getChainHex,
99
shortenAddress,
1010
getAssetReference,
11-
sanitizeOndoTokenName,
1211
} from '../../utils/formatUtils';
1312

1413
/**

0 commit comments

Comments
 (0)