Skip to content

Commit 1d943bf

Browse files
committed
Merge branch 'mmpay_recipient_update' of https://github.com/MetaMask/metamask-mobile into mmpay_recipient_update
2 parents 6e4e095 + 5e05948 commit 1d943bf

20 files changed

Lines changed: 1002 additions & 341 deletions

File tree

app/components/Views/confirmations/components/AccountSelector/AccountSelector.styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const stylesheet = (params: { theme: Theme }) => {
77
return StyleSheet.create({
88
container: {
99
paddingVertical: 12,
10-
paddingHorizontal: 16,
10+
paddingHorizontal: 8,
1111
borderBottomWidth: 1,
1212
borderBottomColor: theme.colors.border.muted,
1313
},
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import React, { act } from 'react';
2+
import { fireEvent } from '@testing-library/react-native';
3+
import { TransactionType } from '@metamask/transaction-controller';
4+
import { Hex } from '@metamask/utils';
5+
import { merge } from 'lodash';
6+
7+
import Engine from '../../../../../core/Engine';
8+
import renderWithProvider from '../../../../../util/test/renderWithProvider';
9+
import { transactionApprovalControllerMock } from '../../__mocks__/controllers/approval-controller-mock';
10+
import { otherControllersMock } from '../../__mocks__/controllers/other-controllers-mock';
11+
import { simpleSendTransactionControllerMock } from '../../__mocks__/controllers/transaction-controller-mock';
12+
import { useTransactionMetadataRequest } from '../../hooks/transactions/useTransactionMetadataRequest';
13+
import { useTransactionAccountOverride } from '../../hooks/transactions/useTransactionAccountOverride';
14+
import PayAccountSelector from './PayAccountSelector';
15+
16+
jest.mock('../../hooks/transactions/useTransactionMetadataRequest');
17+
jest.mock('../../hooks/transactions/useTransactionAccountOverride');
18+
19+
jest.mock('../../../../../core/Engine', () => ({
20+
context: {
21+
TransactionPayController: {
22+
setTransactionConfig: jest.fn(),
23+
},
24+
},
25+
}));
26+
27+
jest.mock('../AccountSelector', () => {
28+
const { TouchableOpacity, Text } = jest.requireActual('react-native');
29+
return {
30+
__esModule: true,
31+
default: ({
32+
onAccountSelected,
33+
selectedAddress,
34+
label,
35+
}: {
36+
onAccountSelected: (address: string) => void;
37+
selectedAddress?: string;
38+
label?: string;
39+
}) => (
40+
<TouchableOpacity
41+
testID="account-selector"
42+
onPress={() => onAccountSelected('0xSelectedAddress')}
43+
>
44+
<Text testID="account-selector-label">{label ?? 'To'}</Text>
45+
<Text testID="account-selector-address">
46+
{selectedAddress ?? 'No selection'}
47+
</Text>
48+
</TouchableOpacity>
49+
),
50+
};
51+
});
52+
53+
const useTransactionMetadataRequestMock = jest.mocked(
54+
useTransactionMetadataRequest,
55+
);
56+
const useTransactionAccountOverrideMock = jest.mocked(
57+
useTransactionAccountOverride,
58+
);
59+
60+
const setTransactionConfigMock = jest.mocked(
61+
Engine.context.TransactionPayController.setTransactionConfig,
62+
);
63+
64+
function render() {
65+
return renderWithProvider(<PayAccountSelector />, {
66+
state: merge(
67+
{},
68+
simpleSendTransactionControllerMock,
69+
transactionApprovalControllerMock,
70+
otherControllersMock,
71+
),
72+
});
73+
}
74+
75+
describe('PayAccountSelector', () => {
76+
beforeEach(() => {
77+
jest.resetAllMocks();
78+
79+
useTransactionMetadataRequestMock.mockReturnValue({
80+
id: 'mock-tx-id',
81+
type: TransactionType.moneyAccountDeposit,
82+
txParams: { from: '0x123' },
83+
} as never);
84+
85+
useTransactionAccountOverrideMock.mockReturnValue(undefined);
86+
});
87+
88+
it('returns null for non-money-account transactions', () => {
89+
useTransactionMetadataRequestMock.mockReturnValue({
90+
id: 'mock-tx-id',
91+
type: TransactionType.simpleSend,
92+
txParams: { from: '0x123' },
93+
} as never);
94+
95+
const { queryByTestId } = render();
96+
expect(queryByTestId('account-selector')).toBeNull();
97+
});
98+
99+
it('renders with no pre-selected address when accountOverride is undefined', () => {
100+
const { getByTestId } = render();
101+
102+
expect(getByTestId('account-selector-address')).toHaveTextContent(
103+
'No selection',
104+
);
105+
});
106+
107+
it('renders with pre-selected address from accountOverride', () => {
108+
useTransactionAccountOverrideMock.mockReturnValue(
109+
'0xOverrideAddress' as Hex,
110+
);
111+
112+
const { getByTestId } = render();
113+
114+
expect(getByTestId('account-selector-address')).toHaveTextContent(
115+
'0xOverrideAddress',
116+
);
117+
});
118+
119+
it('shows "From" label for deposit transactions', () => {
120+
const { getByTestId } = render();
121+
122+
expect(getByTestId('account-selector-label')).toHaveTextContent('From');
123+
});
124+
125+
it('shows default "To" label for withdraw transactions', () => {
126+
useTransactionMetadataRequestMock.mockReturnValue({
127+
id: 'mock-tx-id',
128+
type: TransactionType.moneyAccountWithdraw,
129+
txParams: { from: '0x123' },
130+
} as never);
131+
132+
const { getByTestId } = render();
133+
134+
expect(getByTestId('account-selector-label')).toHaveTextContent('To');
135+
});
136+
137+
it('calls setTransactionConfig with accountOverride on account selection', async () => {
138+
const { getByTestId } = render();
139+
140+
await act(async () => {
141+
fireEvent.press(getByTestId('account-selector'));
142+
});
143+
144+
expect(setTransactionConfigMock).toHaveBeenCalledWith(
145+
'mock-tx-id',
146+
expect.any(Function),
147+
);
148+
149+
const configCallback = setTransactionConfigMock.mock.calls[0][1];
150+
const config = {} as { accountOverride?: Hex; isPostQuote?: boolean };
151+
configCallback(config as never);
152+
153+
expect(config.accountOverride).toBe('0xSelectedAddress');
154+
});
155+
156+
it('sets isPostQuote for withdraw transactions', async () => {
157+
useTransactionMetadataRequestMock.mockReturnValue({
158+
id: 'mock-tx-id',
159+
type: TransactionType.moneyAccountWithdraw,
160+
txParams: { from: '0x123' },
161+
} as never);
162+
163+
const { getByTestId } = render();
164+
165+
await act(async () => {
166+
fireEvent.press(getByTestId('account-selector'));
167+
});
168+
169+
const configCallback = setTransactionConfigMock.mock.calls[0][1];
170+
const config = {} as { accountOverride?: Hex; isPostQuote?: boolean };
171+
configCallback(config as never);
172+
173+
expect(config.accountOverride).toBe('0xSelectedAddress');
174+
expect(config.isPostQuote).toBe(true);
175+
});
176+
177+
it('does not set isPostQuote for deposit transactions', async () => {
178+
const { getByTestId } = render();
179+
180+
await act(async () => {
181+
fireEvent.press(getByTestId('account-selector'));
182+
});
183+
184+
const configCallback = setTransactionConfigMock.mock.calls[0][1];
185+
const config = {} as { accountOverride?: Hex; isPostQuote?: boolean };
186+
configCallback(config as never);
187+
188+
expect(config.accountOverride).toBe('0xSelectedAddress');
189+
expect(config.isPostQuote).toBeUndefined();
190+
});
191+
192+
it('does not call setTransactionConfig when transactionId is missing', async () => {
193+
useTransactionMetadataRequestMock.mockReturnValue({
194+
type: TransactionType.moneyAccountDeposit,
195+
txParams: { from: '0x123' },
196+
} as never);
197+
198+
const { getByTestId } = render();
199+
200+
await act(async () => {
201+
fireEvent.press(getByTestId('account-selector'));
202+
});
203+
204+
expect(setTransactionConfigMock).not.toHaveBeenCalled();
205+
});
206+
});
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React, { useCallback } from 'react';
2+
import { TransactionType } from '@metamask/transaction-controller';
3+
import { Hex } from '@metamask/utils';
4+
5+
import { strings } from '../../../../../../locales/i18n';
6+
import Engine from '../../../../../core/Engine';
7+
import { useTransactionMetadataRequest } from '../../hooks/transactions/useTransactionMetadataRequest';
8+
import { useTransactionAccountOverride } from '../../hooks/transactions/useTransactionAccountOverride';
9+
import { hasTransactionType } from '../../utils/transaction';
10+
import AccountSelector from '../AccountSelector';
11+
12+
const PayAccountSelector: React.FC = () => {
13+
const transactionMeta = useTransactionMetadataRequest();
14+
const transactionId = transactionMeta?.id;
15+
const accountOverride = useTransactionAccountOverride();
16+
17+
const isMoneyAccountWithdraw = hasTransactionType(transactionMeta, [
18+
TransactionType.moneyAccountWithdraw,
19+
]);
20+
const isMoneyAccountDeposit = hasTransactionType(transactionMeta, [
21+
TransactionType.moneyAccountDeposit,
22+
]);
23+
24+
const handleAccountSelected = useCallback(
25+
(address: string) => {
26+
if (transactionId) {
27+
Engine.context.TransactionPayController.setTransactionConfig(
28+
transactionId,
29+
(config) => {
30+
config.accountOverride = address as Hex;
31+
if (isMoneyAccountWithdraw) {
32+
config.isPostQuote = true;
33+
}
34+
},
35+
);
36+
}
37+
},
38+
[transactionId, isMoneyAccountWithdraw],
39+
);
40+
41+
if (!isMoneyAccountDeposit && !isMoneyAccountWithdraw) {
42+
return null;
43+
}
44+
45+
const label = isMoneyAccountDeposit
46+
? strings('confirm.label.from')
47+
: undefined;
48+
49+
return (
50+
<AccountSelector
51+
label={label}
52+
selectedAddress={accountOverride}
53+
onAccountSelected={handleAccountSelected}
54+
/>
55+
);
56+
};
57+
58+
export default PayAccountSelector;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default } from './PayAccountSelector';
2+
export type { PayAccountSelectorProps } from './PayAccountSelector';

app/components/Views/confirmations/components/developer/confirmations-developer-options/confirmations-developer-options.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ describe('ConfirmationsDeveloperOptions', () => {
243243
},
244244
{
245245
params: {
246-
to: MOCK_PROXY_ADDRESS,
246+
to: MOCK_POLYGON_USDCE,
247247
data: MOCK_TRANSFER_DATA,
248248
},
249249
type: TransactionType.moneyAccountDeposit,
@@ -316,7 +316,7 @@ describe('ConfirmationsDeveloperOptions', () => {
316316
},
317317
{
318318
params: {
319-
to: MOCK_PROXY_ADDRESS,
319+
to: MOCK_POLYGON_USDCE,
320320
data: MOCK_TRANSFER_DATA,
321321
},
322322
type: TransactionType.moneyAccountWithdraw,

app/components/Views/confirmations/components/developer/confirmations-developer-options/confirmations-developer-options.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ function MoneyAccountDeposit() {
142142
addTransactionBatchAndNavigate({
143143
loader: ConfirmationLoader.CustomAmount,
144144
transactionType: TransactionType.moneyAccountDeposit,
145-
recipient: PROXY_ADDRESS,
146145
});
147146
}, [addTransactionBatchAndNavigate]);
148147

@@ -164,7 +163,6 @@ function MoneyAccountWithdraw() {
164163
addTransactionBatchAndNavigate({
165164
loader: ConfirmationLoader.CustomAmount,
166165
transactionType: TransactionType.moneyAccountWithdraw,
167-
recipient: PROXY_ADDRESS,
168166
});
169167
}, [addTransactionBatchAndNavigate]);
170168

@@ -192,20 +190,18 @@ function useAddTransactionBatch() {
192190

193191
const transferData = generateTransferData('transfer', {
194192
toAddress: PROXY_ADDRESS,
195-
amount: '0x0',
193+
amount: '0xF4240',
196194
}) as Hex;
197195

198196
const addTransactionBatchAndNavigate = useCallback(
199197
async ({
200198
headerShown,
201199
loader,
202200
transactionType,
203-
recipient = POLYGON_USDCE_ADDRESS,
204201
}: {
205202
headerShown?: boolean;
206203
loader?: ConfirmationLoader;
207204
transactionType: TransactionType;
208-
recipient?: Hex;
209205
}) => {
210206
navigateToConfirmation({
211207
headerShown,
@@ -228,7 +224,7 @@ function useAddTransactionBatch() {
228224
},
229225
{
230226
params: {
231-
to: recipient,
227+
to: POLYGON_USDCE_ADDRESS,
232228
data: transferData,
233229
},
234230
type: transactionType,

0 commit comments

Comments
 (0)