Skip to content

Commit de15c5b

Browse files
chore(runway): cherry-pick fix(Rewards): Ondo campaign UX cp-7.79.0 (#30715)
- fix(Rewards): Ondo campaign UX cp-7.79.0 (#30711) <!-- 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 attempts to fix what was intended to be fixed in PR #30419 but for some reason that PR did not work - to ensure that a source token is used when opening an Ondo swap from Rewards. It also updates the button text to say "Trade now" instead of "Open Position." ## **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** <!-- [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) - [ ] 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** > Copy and Rewards swap prefill logic only; changes are localized with new unit tests and no auth or payment impact. > > **Overview** > Renames the opted-in, no-positions Ondo campaign CTA from **Open Position** to **Trade now** via `rewards.campaign_details.open_position` in `en.json`, with matching test and comment updates. > > Fixes **open position** swaps from Rewards by resolving default source tokens through a new `getOndoOpenPositionSourceToken` helper: chain defaults are keyed by hex (`0x1` → USDC, `0x38` → USDT) instead of CAIP-only map lookups, and the helper accepts hex or `eip155:` chain IDs (including after-hours confirm). USDY balance preference is unchanged; tests cover BNB/USDT and the helper edge cases. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit be88b88. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> [f7e1cce](f7e1cce) Co-authored-by: Christian Montoya <christian.montoya@consensys.net>
1 parent 93346a4 commit de15c5b

6 files changed

Lines changed: 70 additions & 17 deletions

File tree

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ jest.mock('../../../../../locales/i18n', () => ({
460460
'rewards.campaigns_view.error_description': 'Please try again.',
461461
'rewards.campaigns_view.retry_button': 'Retry',
462462
'rewards.campaign_details.join_campaign': 'Join Campaign',
463-
'rewards.campaign_details.open_position': 'Open Position',
463+
'rewards.campaign_details.open_position': 'Trade now',
464464
'rewards.campaign_details.swap_ondo_assets': 'Swap Ondo Assets',
465465
'rewards.campaign_details.ondo.entries_closed_title': 'Entries closed',
466466
'rewards.campaign_details.ondo.entries_closed_description':
@@ -803,7 +803,7 @@ describe('OndoCampaignDetailsView', () => {
803803
expect(getByTestId(CAMPAIGN_CTA_TEST_IDS.CTA_BUTTON)).toBeDefined();
804804
});
805805

806-
it('renders "Open Position" CTA when participant is opted in with no positions', () => {
806+
it('renders "Trade now" CTA when participant is opted in with no positions', () => {
807807
mockUseRewardCampaigns.mockReturnValue({
808808
...hookDefaults,
809809
campaigns: [createTestCampaign()],
@@ -816,7 +816,7 @@ describe('OndoCampaignDetailsView', () => {
816816
});
817817
const { getByTestId, getByText } = render(<OndoCampaignDetailsView />);
818818
expect(getByTestId(CAMPAIGN_CTA_TEST_IDS.CTA_BUTTON)).toBeDefined();
819-
expect(getByText('Open Position')).toBeDefined();
819+
expect(getByText('Trade now')).toBeDefined();
820820
});
821821

822822
it('renders "Swap Ondo Assets" CTA when participant is opted in with positions', () => {

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import React from 'react';
22
import { render, fireEvent } from '@testing-library/react-native';
33
import { useSelector } from 'react-redux';
4-
import OndoCampaignRwaSelectorView from './OndoCampaignRwaSelectorView';
4+
import OndoCampaignRwaSelectorView, {
5+
getOndoOpenPositionSourceToken,
6+
} from './OndoCampaignRwaSelectorView';
57
import type { TrendingAsset } from '@metamask/assets-controllers';
68
import { useAnalytics } from '../../../hooks/useAnalytics/useAnalytics';
79
import {
@@ -674,6 +676,20 @@ describe('OndoCampaignRwaSelectorView', () => {
674676
expect(queryByTestId('after-hours-sheet')).toBeNull();
675677
});
676678

679+
it('uses USDT as the source token for BNB Chain assets when after hours confirm is pressed', () => {
680+
const token = buildToken('AAPL', 'eip155:56/erc20:0xaapl');
681+
mockUseRwaTokens.mockReturnValue({ data: [token], isLoading: false });
682+
const { getByTestId } = render(<OndoCampaignRwaSelectorView />);
683+
fireEvent.press(getByTestId('token-row-AAPL'));
684+
fireEvent.press(getByTestId('after-hours-confirm'));
685+
686+
expect(mockGoToSwaps).toHaveBeenCalledTimes(1);
687+
const [srcArg, destArg] = mockGoToSwaps.mock.calls[0];
688+
expect(srcArg?.symbol).toBe('USDT');
689+
expect(srcArg?.chainId).toBe('0x38');
690+
expect(destArg?.chainId).toBe('eip155:56');
691+
});
692+
677693
it('tracks button_clicked event when after hours confirm is pressed', () => {
678694
const token = buildToken('AAPL');
679695
mockUseRwaTokens.mockReturnValue({ data: [token], isLoading: false });
@@ -711,3 +727,24 @@ describe('OndoCampaignRwaSelectorView', () => {
711727
});
712728
});
713729
});
730+
731+
describe('getOndoOpenPositionSourceToken', () => {
732+
it('returns USDT for BNB Chain when the chain ID is hex', () => {
733+
expect(getOndoOpenPositionSourceToken('0x38')).toEqual(
734+
expect.objectContaining({
735+
symbol: 'USDT',
736+
chainId: '0x38',
737+
}),
738+
);
739+
});
740+
741+
it('returns undefined when no chain ID is provided', () => {
742+
expect(getOndoOpenPositionSourceToken(undefined)).toBeUndefined();
743+
});
744+
745+
it('returns undefined for unsupported non-EVM chain IDs', () => {
746+
expect(
747+
getOndoOpenPositionSourceToken('solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'),
748+
).toBeUndefined();
749+
});
750+
});

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

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ const USDY_CAIP19 =
6868
'eip155:1/erc20:0x96f6ef951840721adbf46ac996b59e0235cb985c' as const;
6969
const USDY_DECIMALS = 18;
7070

71-
const ONDO_OPEN_POSITION_SOURCE_TOKENS: Partial<
72-
Record<CaipChainId, BridgeToken>
71+
const ONDO_OPEN_POSITION_SOURCE_TOKENS_BY_CHAIN_ID: Partial<
72+
Record<Hex, BridgeToken>
7373
> = {
74-
'eip155:1': {
74+
'0x1': {
7575
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
7676
symbol: 'USDC',
7777
name: 'USD Coin',
@@ -80,7 +80,7 @@ const ONDO_OPEN_POSITION_SOURCE_TOKENS: Partial<
8080
image:
8181
'https://static.cx.metamask.io/api/v2/tokenIcons/assets/eip155/1/erc20/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.png',
8282
},
83-
'eip155:56': {
83+
'0x38': {
8484
address: '0x55d398326f99059ff775485246999027b3197955',
8585
symbol: 'USDT',
8686
name: 'Tether USD',
@@ -91,6 +91,22 @@ const ONDO_OPEN_POSITION_SOURCE_TOKENS: Partial<
9191
},
9292
};
9393

94+
export const getOndoOpenPositionSourceToken = (
95+
chainId: BridgeToken['chainId'] | undefined,
96+
): BridgeToken | undefined => {
97+
if (!chainId) return undefined;
98+
99+
if (chainId.startsWith('0x')) {
100+
return ONDO_OPEN_POSITION_SOURCE_TOKENS_BY_CHAIN_ID[chainId as Hex];
101+
}
102+
103+
if (!chainId.startsWith('eip155:')) return undefined;
104+
105+
return ONDO_OPEN_POSITION_SOURCE_TOKENS_BY_CHAIN_ID[
106+
caipChainIdToHex(chainId as CaipChainId)
107+
];
108+
};
109+
94110
// ParamListBase requires an index signature, which interfaces don't support
95111
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
96112
type OndoCampaignRwaSelectorRouteParams = {
@@ -306,7 +322,7 @@ const OndoCampaignRwaSelectorView: React.FC = () => {
306322
};
307323
const openPositionSourceToken =
308324
mode === 'open_position'
309-
? (ondoUsdSrcToken ?? ONDO_OPEN_POSITION_SOURCE_TOKENS[destChainId])
325+
? (ondoUsdSrcToken ?? getOndoOpenPositionSourceToken(destChainId))
310326
: undefined;
311327

312328
if (!isTokenTradingOpen(destToken)) {
@@ -482,9 +498,9 @@ const OndoCampaignRwaSelectorView: React.FC = () => {
482498
const openPositionSourceToken =
483499
mode === 'open_position'
484500
? (ondoUsdSrcToken ??
485-
ONDO_OPEN_POSITION_SOURCE_TOKENS[
486-
afterHoursPendingToken.chainId as CaipChainId
487-
])
501+
getOndoOpenPositionSourceToken(
502+
afterHoursPendingToken.chainId,
503+
))
488504
: undefined;
489505
trackEvent(
490506
createEventBuilder(

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ jest.mock('../../../../../../locales/i18n', () => ({
110110
strings: (key: string) => {
111111
const map: Record<string, string> = {
112112
'rewards.campaign_details.join_campaign': 'Join Campaign',
113-
'rewards.campaign_details.open_position': 'Open Position',
113+
'rewards.campaign_details.open_position': 'Trade now',
114114
'rewards.campaign_details.swap_ondo_assets': 'Swap Ondo Assets',
115115
'rewards.campaign_details.ondo.entries_closed_title': 'Entries closed',
116116
'rewards.campaign_details.ondo.entries_closed_description':
@@ -222,7 +222,7 @@ describe('OndoCampaignCTA', () => {
222222
});
223223

224224
describe('opted in, no portfolio positions', () => {
225-
it('renders the "Open Position" button', () => {
225+
it('renders the "Trade now" button', () => {
226226
const { getByTestId, getByText } = render(
227227
<OndoCampaignCTA
228228
campaign={buildCampaign()}
@@ -233,7 +233,7 @@ describe('OndoCampaignCTA', () => {
233233
);
234234

235235
expect(getByTestId(CAMPAIGN_CTA_TEST_IDS.CTA_BUTTON)).toBeOnTheScreen();
236-
expect(getByText('Open Position')).toBeOnTheScreen();
236+
expect(getByText('Trade now')).toBeOnTheScreen();
237237
});
238238

239239
it('navigates to RWA asset selector in open_position mode when pressed', () => {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ interface OndoCampaignCTAProps {
3535
* Renders one of four states depending on campaign/participant status:
3636
* - Delegates to CampaignOptInCta for the opt-in flow (active, not opted in, within deposit window). Passes ONDO_RESTRICTED_COUNTRIES so that CampaignOptInCta shows the geo-locked "Check eligibility" CTA for restricted users.
3737
* - "Entries closed" button (with Lock icon + toast) when cutoff has passed and user is not opted in
38-
* - "Open Position" button when the user has opted in but has no portfolio positions
38+
* - "Trade now" button when the user has opted in but has no portfolio positions
3939
* - "Swap Ondo Assets" button when the user has opted in and has portfolio positions
4040
*/
4141
const OndoCampaignCTA: React.FC<OndoCampaignCTAProps> = ({

locales/languages/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8827,7 +8827,7 @@
88278827
"checking_opt_in_status": "Checking opt in status",
88288828
"swap": "Swap",
88298829
"swap_ondo_assets": "Swap Ondo Assets",
8830-
"open_position": "Open Position",
8830+
"open_position": "Trade now",
88318831
"how_it_works": "How it works",
88328832
"ondo": {
88338833
"entries_closed_title": "Entries closed",

0 commit comments

Comments
 (0)