Skip to content

Commit 9b6cdfe

Browse files
committed
feat(money): wire up real transaction activity on Money Home (MUSD-660)
1 parent c66572b commit 9b6cdfe

15 files changed

Lines changed: 739 additions & 61 deletions

File tree

app/components/UI/Money/Views/MoneyHomeView/MoneyHomeView.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import styleSheet from './MoneyHomeView.styles';
2626
import { useMusdConversionTokens } from '../../../Earn/hooks/useMusdConversionTokens';
2727
import { useMusdConversion } from '../../../Earn/hooks/useMusdConversion';
2828
import { useMoneyAccountTransactions } from '../../hooks/useMoneyAccountTransactions';
29-
import { showMoneyActivityUnderConstructionAlert } from '../../constants/showMoneyActivityUnderConstructionAlert';
3029
import useMoneyAccountBalance from '../../hooks/useMoneyAccountBalance';
3130
import { selectCurrentCurrency } from '../../../../../selectors/currencyRateController';
3231
import { moneyFormatFiat } from '../../utils/moneyFormatFiat';
@@ -207,9 +206,15 @@ const MoneyHomeView = () => {
207206
}, [navigation]);
208207
const handleActivityHeaderPress = handleViewAllActivityPress;
209208

210-
const handleActivityItemPress = useCallback(() => {
211-
showMoneyActivityUnderConstructionAlert();
212-
}, []);
209+
const handleActivityItemPress = useCallback(
210+
(transactionId: string) => {
211+
navigation.navigate(Routes.MONEY.MODALS.ROOT, {
212+
screen: Routes.MONEY.MODALS.TRANSACTION_DETAILS_SHEET,
213+
params: { transactionId },
214+
});
215+
},
216+
[navigation],
217+
);
213218

214219
const handleOnboardingCtaPress = useCallback(() => {
215220
if (isCardholderWithMilestone) {

app/components/UI/Money/components/MoneyActivityItem/MoneyActivityItem.tsx

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { MoneyActivityItemTestIds } from './MoneyActivityItem.testIds';
2828
export interface MoneyActivityItemProps {
2929
tx: TransactionMeta;
3030
moneyAddress: string | undefined;
31-
onPress?: () => void;
31+
onPress?: (transactionId: string) => void;
3232
/** When true, shows the chain network badge on the token avatar. Defaults to false. */
3333
showNetworkBadge?: boolean;
3434
}
@@ -51,14 +51,27 @@ const MoneyActivityItem = ({
5151
[tx.chainId, showNetworkBadge],
5252
);
5353

54+
// For ERC-20 source tokens use the token's own image URI.
55+
// For native tokens (e.g. ETH) fall back to the source chain's network icon.
56+
// If neither is available, fall back to the mUSD icon.
57+
const tokenAvatarImageSource = useMemo(() => {
58+
if (display.sourceTokenImage) {
59+
return { uri: display.sourceTokenImage };
60+
}
61+
if (display.sourceTokenChainId) {
62+
return getNetworkImageSource({ chainId: display.sourceTokenChainId });
63+
}
64+
return MUSD_TOKEN.imageSource;
65+
}, [display.sourceTokenImage, display.sourceTokenChainId]);
66+
5467
const amountColor = display.isIncoming
5568
? TextColor.SuccessDefault
5669
: TextColor.TextDefault;
5770

5871
return (
5972
<Pressable
6073
accessibilityRole="button"
61-
onPress={onPress}
74+
onPress={onPress ? () => onPress(tx.id) : undefined}
6275
testID={`${MoneyActivityItemTestIds.ROW}-${tx.id}`}
6376
style={({ pressed }) =>
6477
tw.style(
@@ -81,16 +94,16 @@ const MoneyActivityItem = ({
8194
}
8295
>
8396
<AvatarToken
84-
name={MUSD_TOKEN.name}
85-
imageSource={MUSD_TOKEN.imageSource}
97+
name={display.sourceTokenSymbol ?? MUSD_TOKEN.name}
98+
imageSource={tokenAvatarImageSource}
8699
size={AvatarSize.Lg}
87100
/>
88101
</BadgeWrapper>
89102
) : (
90103
<Box twClassName="self-center">
91104
<AvatarToken
92-
name={MUSD_TOKEN.name}
93-
imageSource={MUSD_TOKEN.imageSource}
105+
name={display.sourceTokenSymbol ?? MUSD_TOKEN.name}
106+
imageSource={tokenAvatarImageSource}
94107
size={AvatarSize.Lg}
95108
/>
96109
</Box>

app/components/UI/Money/components/MoneyActivityList/MoneyActivityList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface MoneyActivityListProps {
1717
moneyAddress?: string;
1818
onViewAllPress?: () => void;
1919
onHeaderPress?: () => void;
20-
onItemPress?: () => void;
20+
onItemPress?: (transactionId: string) => void;
2121
}
2222

2323
const MoneyActivityList = ({
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import React, { useCallback, useRef } from 'react';
2+
import { useNavigation } from '@react-navigation/native';
3+
import {
4+
BottomSheet,
5+
type BottomSheetRef,
6+
BottomSheetHeader,
7+
Text,
8+
TextVariant,
9+
} from '@metamask/design-system-react-native';
10+
import {
11+
type TransactionMeta,
12+
TransactionType,
13+
} from '@metamask/transaction-controller';
14+
import { strings } from '../../../../../../locales/i18n';
15+
import { TransactionDetails } from '../../../../Views/confirmations/components/activity/transaction-details/transaction-details';
16+
import { useTransactionDetails } from '../../../../Views/confirmations/hooks/activity/useTransactionDetails';
17+
18+
const TITLE_KEYS: Partial<Record<TransactionType, string>> = {
19+
[TransactionType.moneyAccountDeposit]:
20+
'transaction_details.title.money_account_deposit',
21+
[TransactionType.moneyAccountWithdraw]:
22+
'transaction_details.title.money_account_withdraw',
23+
[TransactionType.musdConversion]: 'transaction_details.title.musd_conversion',
24+
[TransactionType.musdClaim]: 'transaction_details.title.musd_claim',
25+
[TransactionType.perpsDeposit]: 'transaction_details.title.perps_deposit',
26+
[TransactionType.perpsWithdraw]: 'transaction_details.title.perps_withdraw',
27+
[TransactionType.predictClaim]: 'transaction_details.title.predict_claim',
28+
[TransactionType.predictDeposit]: 'transaction_details.title.predict_deposit',
29+
[TransactionType.predictWithdraw]:
30+
'transaction_details.title.predict_withdraw',
31+
};
32+
33+
function getTitle(tx: TransactionMeta | undefined): string {
34+
const type =
35+
tx?.type === TransactionType.batch
36+
? ((tx.nestedTransactions?.find((n) => n.type && n.type in TITLE_KEYS)
37+
?.type as TransactionType | undefined) ?? tx.type)
38+
: tx?.type;
39+
return strings(
40+
(type && TITLE_KEYS[type]) ?? 'transaction_details.title.default',
41+
);
42+
}
43+
44+
const MoneyTransactionDetailsSheet = () => {
45+
const sheetRef = useRef<BottomSheetRef>(null);
46+
const navigation = useNavigation();
47+
const { transactionMeta } = useTransactionDetails();
48+
const title = getTitle(transactionMeta);
49+
50+
const handleClose = useCallback(() => {
51+
sheetRef.current?.onCloseBottomSheet();
52+
}, []);
53+
54+
return (
55+
<BottomSheet
56+
ref={sheetRef}
57+
isFullscreen
58+
goBack={navigation.goBack}
59+
keyboardAvoidingViewEnabled={false}
60+
>
61+
<BottomSheetHeader onClose={handleClose}>
62+
<Text variant={TextVariant.HeadingMd}>{title}</Text>
63+
</BottomSheetHeader>
64+
<TransactionDetails />
65+
</BottomSheet>
66+
);
67+
};
68+
69+
export default MoneyTransactionDetailsSheet;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from './MoneyTransactionDetailsSheet';

app/components/UI/Money/constants/activityStyles.test.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ describe('activityStyles', () => {
5050
).toBe('-');
5151
});
5252

53+
it('returns minus for a batch tx with an outgoing nested type', () => {
54+
expect(
55+
getMoneyAmountPrefixForTransactionMeta(
56+
makeTx(TransactionType.batch, {
57+
nestedTransactions: [
58+
{ type: TransactionType.moneyAccountWithdraw } as TransactionMeta,
59+
],
60+
}),
61+
),
62+
).toBe('-');
63+
});
64+
5365
it('returns plus for other classified types', () => {
5466
expect(
5567
getMoneyAmountPrefixForTransactionMeta(
@@ -97,7 +109,7 @@ describe('activityStyles', () => {
97109
).toBe(false);
98110
});
99111

100-
it('matches incoming deposit and conversion types', () => {
112+
it('matches incoming and moneyAccountDeposit types', () => {
101113
expect(
102114
isIncomingMoneyTransactionMeta(makeTx(TransactionType.incoming)),
103115
).toBe(true);
@@ -106,11 +118,38 @@ describe('activityStyles', () => {
106118
makeTx(TransactionType.moneyAccountDeposit),
107119
),
108120
).toBe(true);
121+
});
122+
123+
it('returns false for musdConversion (no longer classified as incoming)', () => {
109124
expect(
110125
isIncomingMoneyTransactionMeta(makeTx(TransactionType.musdConversion)),
126+
).toBe(false);
127+
});
128+
129+
it('returns true for a batch tx with a nested moneyAccountDeposit', () => {
130+
expect(
131+
isIncomingMoneyTransactionMeta(
132+
makeTx(TransactionType.batch, {
133+
nestedTransactions: [
134+
{ type: TransactionType.moneyAccountDeposit } as TransactionMeta,
135+
],
136+
}),
137+
),
111138
).toBe(true);
112139
});
113140

141+
it('returns false for a batch tx with no deposit nested type', () => {
142+
expect(
143+
isIncomingMoneyTransactionMeta(
144+
makeTx(TransactionType.batch, {
145+
nestedTransactions: [
146+
{ type: TransactionType.simpleSend } as TransactionMeta,
147+
],
148+
}),
149+
),
150+
).toBe(false);
151+
});
152+
114153
it('returns false for withdraw', () => {
115154
expect(
116155
isIncomingMoneyTransactionMeta(

app/components/UI/Money/constants/activityStyles.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
} from '@metamask/transaction-controller';
55
import I18n from '../../../../../locales/i18n';
66
import { getIntlNumberFormatter } from '../../../../util/intl';
7-
import { fromTokenMinimalUnit } from '../../../../util/number';
7+
import { fromTokenMinimalUnit } from '../../../../util/number/bigint';
88

99
function formatNumber(num: number): string {
1010
return getIntlNumberFormatter(I18n.locale, {
@@ -19,13 +19,24 @@ const OUTGOING_EVM_TYPES: EvmTransactionType[] = [
1919
EvmTransactionType.simpleSend,
2020
];
2121

22+
function hasOutgoingNestedType(tx: TransactionMeta): boolean {
23+
return (
24+
tx.nestedTransactions?.some(
25+
(nested) => nested.type && OUTGOING_EVM_TYPES.includes(nested.type),
26+
) ?? false
27+
);
28+
}
29+
2230
/**
2331
* +/- prefix for Money rows backed by {@link TransactionMeta} (mUSD pegged 1:1 to USD).
2432
*/
2533
export function getMoneyAmountPrefixForTransactionMeta(
2634
tx: TransactionMeta,
2735
): string {
28-
if (tx.type && OUTGOING_EVM_TYPES.includes(tx.type)) {
36+
if (
37+
(tx.type && OUTGOING_EVM_TYPES.includes(tx.type)) ||
38+
hasOutgoingNestedType(tx)
39+
) {
2940
return '-';
3041
}
3142
return '+';
@@ -49,10 +60,16 @@ export function getMusdDisplayAmountFromTransactionMeta(
4960

5061
export function isIncomingMoneyTransactionMeta(tx: TransactionMeta): boolean {
5162
const t = tx.type;
52-
if (!t) return false;
53-
return (
63+
if (
5464
t === EvmTransactionType.incoming ||
55-
t === EvmTransactionType.moneyAccountDeposit ||
56-
t === EvmTransactionType.musdConversion
65+
t === EvmTransactionType.moneyAccountDeposit
66+
) {
67+
return true;
68+
}
69+
// EIP-7702 batch deposits: moneyAccountDeposit sits in nestedTransactions
70+
return (
71+
tx.nestedTransactions?.some(
72+
(nested) => nested.type === EvmTransactionType.moneyAccountDeposit,
73+
) ?? false
5774
);
5875
}

app/components/UI/Money/constants/moneyActivityFilters.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function tx(overrides: Partial<TransactionMeta>): TransactionMeta {
2323

2424
describe('moneyActivityFilters', () => {
2525
describe('isMoneyActivityDeposit', () => {
26-
it('returns true for incoming, moneyAccountDeposit, and musdConversion', () => {
26+
it('returns true for incoming and moneyAccountDeposit', () => {
2727
expect(
2828
isMoneyActivityDeposit(tx({ type: TransactionType.incoming })),
2929
).toBe(true);
@@ -32,8 +32,24 @@ describe('moneyActivityFilters', () => {
3232
tx({ type: TransactionType.moneyAccountDeposit }),
3333
),
3434
).toBe(true);
35+
});
36+
37+
it('returns false for musdConversion (no longer classified as a deposit)', () => {
3538
expect(
3639
isMoneyActivityDeposit(tx({ type: TransactionType.musdConversion })),
40+
).toBe(false);
41+
});
42+
43+
it('returns true for a batch tx with a nested moneyAccountDeposit', () => {
44+
expect(
45+
isMoneyActivityDeposit(
46+
tx({
47+
type: TransactionType.batch,
48+
nestedTransactions: [
49+
{ type: TransactionType.moneyAccountDeposit } as TransactionMeta,
50+
],
51+
}),
52+
),
3753
).toBe(true);
3854
});
3955

@@ -64,6 +80,19 @@ describe('moneyActivityFilters', () => {
6480
).toBe(true);
6581
});
6682

83+
it('returns true for a batch tx with a nested moneyAccountWithdraw', () => {
84+
expect(
85+
isMoneyActivityTransfer(
86+
tx({
87+
type: TransactionType.batch,
88+
nestedTransactions: [
89+
{ type: TransactionType.moneyAccountWithdraw } as TransactionMeta,
90+
],
91+
}),
92+
),
93+
).toBe(true);
94+
});
95+
6796
it('returns false for deposit-like types', () => {
6897
expect(
6998
isMoneyActivityTransfer(

app/components/UI/Money/constants/moneyActivityFilters.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,33 @@ import {
55

66
export function isMoneyActivityDeposit(tx: TransactionMeta): boolean {
77
const t = tx.type;
8-
return (
8+
if (
99
t === TransactionType.incoming ||
10-
t === TransactionType.moneyAccountDeposit ||
11-
t === TransactionType.musdConversion
10+
t === TransactionType.moneyAccountDeposit
11+
) {
12+
return true;
13+
}
14+
// EIP-7702 batch deposits: moneyAccountDeposit sits in nestedTransactions
15+
return (
16+
tx.nestedTransactions?.some(
17+
(nested) => nested.type === TransactionType.moneyAccountDeposit,
18+
) ?? false
1219
);
1320
}
1421

1522
export function isMoneyActivityTransfer(tx: TransactionMeta): boolean {
1623
const t = tx.type;
17-
return (
24+
if (
1825
t === TransactionType.moneyAccountWithdraw ||
1926
t === TransactionType.simpleSend
27+
) {
28+
return true;
29+
}
30+
// EIP-7702 batch withdrawals: moneyAccountWithdraw sits in nestedTransactions
31+
return (
32+
tx.nestedTransactions?.some(
33+
(nested) => nested.type === TransactionType.moneyAccountWithdraw,
34+
) ?? false
2035
);
2136
}
2237

app/components/UI/Money/hooks/useMoneyAccountTransactions.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ function engineState(
5252
moneyAccounts: MOCK_MONEY_ACCOUNTS,
5353
},
5454
KeyringController: MOCK_KEYRING_CONTROLLER,
55+
TransactionController: {
56+
transactions: [],
57+
},
5558
},
5659
},
5760
} as ProviderValues['state'];

0 commit comments

Comments
 (0)