Skip to content

Commit 8674c24

Browse files
authored
Merge branch 'main' into skip-cash-out-predict-test
2 parents 4f7ed81 + 1623d46 commit 8674c24

File tree

25 files changed

+1097
-598
lines changed

25 files changed

+1097
-598
lines changed

app/component-library/components-temp/MultichainAccounts/AccountCell/AccountCell.tsx

Lines changed: 75 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AccountGroupObject } from '@metamask/account-tree-controller';
22
import React, { useCallback, useMemo } from 'react';
3-
import { TouchableOpacity, View } from 'react-native';
3+
import { type ImageSourcePropType, TouchableOpacity, View } from 'react-native';
44
import { useSelector } from 'react-redux';
55
import { useNavigation } from '@react-navigation/native';
66
import { useStyles } from '../../../hooks';
@@ -36,18 +36,24 @@ interface AccountCellProps {
3636
avatarAccountType: AvatarAccountType;
3737
hideMenu?: boolean;
3838
startAccessory?: React.ReactNode;
39+
endContainer?: React.ReactNode;
3940
chainId?: string;
4041
onSelectAccount?: () => void;
4142
}
4243

43-
const AccountCell = ({
44+
type BalanceEndContainerProps = Pick<
45+
AccountCellProps,
46+
'accountGroup' | 'hideMenu' | 'onSelectAccount'
47+
> & {
48+
networkImageSource?: ImageSourcePropType;
49+
};
50+
51+
const BalanceEndContainer = ({
4452
accountGroup,
45-
avatarAccountType,
46-
hideMenu = false,
47-
startAccessory,
48-
chainId,
53+
hideMenu,
4954
onSelectAccount,
50-
}: AccountCellProps) => {
55+
networkImageSource,
56+
}: BalanceEndContainerProps) => {
5157
const { styles } = useStyles(styleSheet, {});
5258
const { navigate } = useNavigation();
5359

@@ -63,12 +69,6 @@ const AccountCell = ({
6369
const totalBalance = groupBalance?.totalBalanceInUserCurrency;
6470
const userCurrency = groupBalance?.userCurrency;
6571

66-
const selectEvmAddress = useMemo(
67-
() => selectIconSeedAddressByAccountGroupId(accountGroup.id),
68-
[accountGroup.id],
69-
);
70-
const evmAddress = useSelector(selectEvmAddress);
71-
7272
const displayBalance = useMemo(() => {
7373
if (totalBalance == null || !userCurrency) {
7474
return undefined;
@@ -79,6 +79,61 @@ const AccountCell = ({
7979
});
8080
}, [totalBalance, userCurrency]);
8181

82+
return (
83+
<>
84+
<TouchableOpacity onPress={onSelectAccount}>
85+
<View style={styles.balanceContainer}>
86+
<Text
87+
variant={TextVariant.BodyMDMedium}
88+
color={TextColor.Default}
89+
testID={AccountCellIds.BALANCE}
90+
>
91+
{totalBalance ? displayBalance : null}
92+
</Text>
93+
{networkImageSource && (
94+
<Avatar
95+
variant={AvatarVariant.Network}
96+
size={AvatarSize.Xs}
97+
style={styles.networkBadge}
98+
imageSource={networkImageSource}
99+
/>
100+
)}
101+
</View>
102+
</TouchableOpacity>
103+
{!hideMenu && (
104+
<TouchableOpacity
105+
testID={AccountCellIds.MENU}
106+
style={styles.menuButton}
107+
onPress={handleMenuPress}
108+
>
109+
<Icon
110+
name={IconName.MoreVertical}
111+
size={IconSize.Md}
112+
color={TextColor.Alternative}
113+
/>
114+
</TouchableOpacity>
115+
)}
116+
</>
117+
);
118+
};
119+
120+
const AccountCell = ({
121+
accountGroup,
122+
avatarAccountType,
123+
hideMenu = false,
124+
startAccessory,
125+
endContainer,
126+
chainId,
127+
onSelectAccount,
128+
}: AccountCellProps) => {
129+
const { styles } = useStyles(styleSheet, {});
130+
131+
const selectEvmAddress = useMemo(
132+
() => selectIconSeedAddressByAccountGroupId(accountGroup.id),
133+
[accountGroup.id],
134+
);
135+
const evmAddress = useSelector(selectEvmAddress);
136+
82137
// Determine which account address and network avatar to display based on the chainId
83138
let networkAccountAddress;
84139
let networkImageSource;
@@ -138,37 +193,13 @@ const AccountCell = ({
138193
</View>
139194
</TouchableOpacity>
140195
<View style={styles.endContainer}>
141-
<TouchableOpacity onPress={onSelectAccount}>
142-
<View style={styles.balanceContainer}>
143-
<Text
144-
variant={TextVariant.BodyMDMedium}
145-
color={TextColor.Default}
146-
testID={AccountCellIds.BALANCE}
147-
>
148-
{totalBalance ? displayBalance : null}
149-
</Text>
150-
{networkImageSource && (
151-
<Avatar
152-
variant={AvatarVariant.Network}
153-
size={AvatarSize.Xs}
154-
style={styles.networkBadge}
155-
imageSource={networkImageSource}
156-
/>
157-
)}
158-
</View>
159-
</TouchableOpacity>
160-
{!hideMenu && (
161-
<TouchableOpacity
162-
testID={AccountCellIds.MENU}
163-
style={styles.menuButton}
164-
onPress={handleMenuPress}
165-
>
166-
<Icon
167-
name={IconName.MoreVertical}
168-
size={IconSize.Md}
169-
color={TextColor.Alternative}
170-
/>
171-
</TouchableOpacity>
196+
{endContainer || (
197+
<BalanceEndContainer
198+
accountGroup={accountGroup}
199+
hideMenu={hideMenu}
200+
onSelectAccount={onSelectAccount}
201+
networkImageSource={networkImageSource}
202+
/>
172203
)}
173204
</View>
174205
</Box>

app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/AccountListHeader/AccountListHeader.tsx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,23 @@ import Text, {
99
import createStyles from '../MultichainAccountSelectorList.styles';
1010
import { AccountListHeaderProps } from './AccountListHeader.types';
1111

12-
const AccountListHeader = memo(({ title }: AccountListHeaderProps) => {
13-
const { styles } = useStyles(createStyles, {});
12+
const AccountListHeader = memo(
13+
({ title, containerStyle }: AccountListHeaderProps) => {
14+
const { styles } = useStyles(createStyles, {});
1415

15-
return (
16-
<View style={styles.sectionHeader}>
17-
<Text
18-
variant={TextVariant.BodyMDMedium}
19-
color={TextColor.Alternative}
20-
style={styles.sectionHeaderText}
21-
>
22-
{title}
23-
</Text>
24-
</View>
25-
);
26-
});
16+
return (
17+
<View style={[styles.sectionHeader, containerStyle]}>
18+
<Text
19+
variant={TextVariant.BodyMDMedium}
20+
color={TextColor.Alternative}
21+
style={styles.sectionHeaderText}
22+
>
23+
{title}
24+
</Text>
25+
</View>
26+
);
27+
},
28+
);
2729

2830
AccountListHeader.displayName = 'AccountListHeader';
2931

Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { ViewStyle } from 'react-native';
2+
13
export interface AccountListHeaderProps {
24
title: string;
5+
containerStyle?: ViewStyle;
36
}

app/components/UI/Perps/controllers/providers/HyperLiquidProvider.test.ts

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,7 +2616,7 @@ describe('HyperLiquidProvider', () => {
26162616
const result = await provider.updatePositionTPSL(updateParams);
26172617

26182618
expect(result.success).toBe(false);
2619-
expect(result.error).toContain('Failed to fetch market metadata');
2619+
expect(result.error).toContain('Invalid meta response');
26202620
});
26212621

26222622
it('should handle meta response without universe property', async () => {
@@ -2634,7 +2634,7 @@ describe('HyperLiquidProvider', () => {
26342634
const result = await provider.updatePositionTPSL(updateParams);
26352635

26362636
expect(result.success).toBe(false);
2637-
expect(result.error).toContain('Failed to fetch market metadata');
2637+
expect(result.error).toContain('Invalid meta response');
26382638
});
26392639
});
26402640

@@ -4266,20 +4266,27 @@ describe('HyperLiquidProvider', () => {
42664266

42674267
expect(result.success).toBe(true);
42684268

4269-
// Verify builder fee approval was called
4269+
// Builder fee approval is set once during ensureReady() initialization
4270+
// With session caching, it should be called once (during first ensureReady)
42704271
expect(
42714272
mockClientService.getExchangeClient().approveBuilderFee,
42724273
).toHaveBeenCalledWith({
42734274
builder: expect.any(String),
42744275
maxFeeRate: expect.stringContaining('%'),
42754276
});
42764277

4277-
// Verify referral code was set
4278-
expect(
4279-
mockClientService.getExchangeClient().setReferrer,
4280-
).toHaveBeenCalledWith({
4281-
code: expect.any(String),
4282-
});
4278+
// Note: Referral setup is fire-and-forget (non-blocking), so we can't reliably
4279+
// test it synchronously. It's tested separately in dedicated referral tests.
4280+
4281+
// Place a second order to verify caching (should NOT call builder fee approval again)
4282+
const mockExchangeClient = mockClientService.getExchangeClient();
4283+
(mockExchangeClient.approveBuilderFee as jest.Mock).mockClear();
4284+
4285+
const result2 = await provider.placeOrder(orderParams);
4286+
4287+
expect(result2.success).toBe(true);
4288+
// Session cache prevents redundant builder fee approval calls
4289+
expect(mockExchangeClient.approveBuilderFee).not.toHaveBeenCalled();
42834290

42844291
// Verify order was placed with builder fee
42854292
expect(mockClientService.getExchangeClient().order).toHaveBeenCalledWith(
@@ -4450,7 +4457,7 @@ describe('HyperLiquidProvider', () => {
44504457
expect(result.error).toContain('Builder fee approval failed');
44514458
});
44524459

4453-
it('should handle referral code setup failure', async () => {
4460+
it('should handle referral code setup failure (non-blocking)', async () => {
44544461
// Mock builder fee already approved
44554462
mockClientService.getInfoClient = jest
44564463
.fn()
@@ -4483,8 +4490,9 @@ describe('HyperLiquidProvider', () => {
44834490

44844491
const result = await provider.placeOrder(orderParams);
44854492

4486-
expect(result.success).toBe(false);
4487-
expect(result.error).toContain('Error ensuring referral code is set');
4493+
// Referral setup is now non-blocking (fire-and-forget), so order should succeed
4494+
expect(result.success).toBe(true);
4495+
expect(result.orderId).toBeDefined();
44884496
});
44894497

44904498
it('should skip referral setup when referral code is not ready', async () => {
@@ -4574,6 +4582,11 @@ describe('HyperLiquidProvider', () => {
45744582

45754583
it('should properly transform getOrders with reduceOnly and isTrigger fields', async () => {
45764584
mockClientService.getInfoClient = jest.fn().mockReturnValue({
4585+
maxBuilderFee: jest.fn().mockResolvedValue(1),
4586+
referral: jest.fn().mockResolvedValue({
4587+
referrerState: { stage: 'ready', data: { code: 'MMCSI' } },
4588+
referredBy: { code: 'MMCSI' },
4589+
}),
45774590
historicalOrders: jest.fn().mockResolvedValue([
45784591
{
45794592
order: {
@@ -4675,6 +4688,11 @@ describe('HyperLiquidProvider', () => {
46754688

46764689
it('should properly transform getOpenOrders with reduceOnly and isTrigger fields', async () => {
46774690
mockClientService.getInfoClient = jest.fn().mockReturnValue({
4691+
maxBuilderFee: jest.fn().mockResolvedValue(1),
4692+
referral: jest.fn().mockResolvedValue({
4693+
referrerState: { stage: 'ready', data: { code: 'MMCSI' } },
4694+
referredBy: { code: 'MMCSI' },
4695+
}),
46784696
clearinghouseState: jest.fn().mockResolvedValue({
46794697
marginSummary: { totalMarginUsed: '500', accountValue: '10500' },
46804698
withdrawable: '9500',
@@ -4993,6 +5011,11 @@ describe('HyperLiquidProvider', () => {
49935011
},
49945012
]);
49955013
mockClientService.getInfoClient = jest.fn().mockReturnValue({
5014+
maxBuilderFee: jest.fn().mockResolvedValue(1),
5015+
referral: jest.fn().mockResolvedValue({
5016+
referrerState: { stage: 'ready', data: { code: 'MMCSI' } },
5017+
referredBy: { code: 'MMCSI' },
5018+
}),
49965019
frontendOpenOrders: mockFrontendOpenOrders,
49975020
clearinghouseState: jest.fn().mockResolvedValue({
49985021
marginSummary: { totalMarginUsed: '0', accountValue: '1000' },
@@ -5073,6 +5096,11 @@ describe('HyperLiquidProvider', () => {
50735096
]);
50745097
});
50755098
mockClientService.getInfoClient = jest.fn().mockReturnValue({
5099+
maxBuilderFee: jest.fn().mockResolvedValue(1),
5100+
referral: jest.fn().mockResolvedValue({
5101+
referrerState: { stage: 'ready', data: { code: 'MMCSI' } },
5102+
referredBy: { code: 'MMCSI' },
5103+
}),
50765104
frontendOpenOrders: mockFrontendOpenOrders,
50775105
clearinghouseState: jest.fn().mockResolvedValue({
50785106
marginSummary: { totalMarginUsed: '0', accountValue: '1000' },

0 commit comments

Comments
 (0)