Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1494,6 +1494,7 @@ const ALL_KINDS: ActivityListItem['type'][] = [
'claim',
'claimMusdBonus',
'deposit',
'stake',
'unstake',
'convert',
'wrap',
Expand Down Expand Up @@ -1546,7 +1547,8 @@ const EXPECTED_TITLES = {
claim: 'Claimed',
claimMusdBonus: strings('transactions.activity_claim_musd_bonus'),
deposit: 'Deposited',
unstake: 'Unstaked',
stake: 'Staked Ethereum',
unstake: 'Unstaked Ethereum',
convert: 'Converted',
wrap: strings('transactions.activity_wrap'),
unwrap: strings('transactions.activity_unwrap'),
Expand Down
1 change: 1 addition & 0 deletions app/components/UI/ActivityListItemRow/resolveIconType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export function resolveIconType(type: ActivityKind): string {
case 'nftSell':
case 'lendingDeposit':
case 'deposit':
case 'stake':
case 'wrap':
case 'perpsAddFunds':
case 'predictionsAddFunds':
Expand Down
72 changes: 72 additions & 0 deletions app/components/UI/ActivityListItemRow/titleLabels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { strings } from '../../../../locales/i18n';

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pulling out resolvers from app/components/UI/ActivityListItemRow/useActivityListItemRowContent.ts

import type { ActivityKind } from '../../../util/activity-adapters';

/**
* Fallback title resolvers for activity kinds whose title comes straight from an
* i18n string (perps/predict domain rows). Consumed by `resolveFallbackTitle` in
* `useActivityListItemRowContent`.
*/
export const ACTIVITY_FALLBACK_TITLE_RESOLVERS: Partial<
Record<ActivityKind, () => string>
> = {
predictionsAddFunds: () =>
strings('transactions.activity_prediction_account_funded'),
predictionsWithdrawFunds: () =>
strings('transactions.activity_prediction_withdrawal'),
predictionClaimWinnings: () => strings('predict.transactions.claim_title'),
predictionCashedOut: () => strings('predict.transactions.sell_title'),
// Design board copy: "Prediction placed" (not the legacy "Predicted").
predictionPlaced: () => strings('transactions.activity_prediction_placed'),
perpsAddFunds: () => strings('transactions.activity_perps_account_funded'),
perpsWithdraw: () => strings('transactions.activity_perps_withdrawal'),
perpsOpenLong: () => strings('transactions.activity_perps_open_long'),
perpsCloseLong: () => strings('transactions.activity_perps_close_long'),
perpsCloseLongLiquidated: () =>
strings('transactions.activity_perps_close_long_liquidated'),
perpsCloseLongStopLoss: () =>
strings('transactions.activity_perps_close_long_stop_loss'),
perpsOpenShort: () => strings('transactions.activity_perps_open_short'),
perpsCloseShort: () => strings('transactions.activity_perps_close_short'),
perpsCloseShortLiquidated: () =>
strings('transactions.activity_perps_close_short_liquidated'),
perpsCloseShortStopLoss: () =>
strings('transactions.activity_perps_close_short_stop_loss'),
perpsPaidFundingFees: () =>
strings('transactions.activity_perps_paid_funding_fees'),
perpsReceivedFundingFees: () =>
strings('transactions.activity_perps_received_funding_fees'),
perpsCloseShortTakeProfit: () =>
strings('transactions.activity_perps_close_short_take_profit'),
perpsCloseLongTakeProfit: () =>
strings('transactions.activity_perps_close_long_take_profit'),
marketShort: () => strings('transactions.activity_market_short'),
stopMarketCloseShort: () =>
strings('transactions.activity_stop_market_close_short'),
marketCloseShort: () => strings('transactions.activity_market_close_short'),
limitShort: () => strings('transactions.activity_limit_short'),
limitCloseShort: () => strings('transactions.activity_limit_close_short'),
};

/**
* Status verbs for single-token value activities (buy/sell/claim/stake/unstake/
* deposit). Keyed by kind so the resolver is a lookup instead of a ternary chain.
*/
export const TOKEN_ACTION_LABELS: Record<
'buy' | 'sell' | 'claim' | 'stake' | 'unstake' | 'deposit',
{ success: string; pending: string; failed: string }
> = {
buy: { success: 'Bought', pending: 'Buying', failed: 'Buy failed' },
sell: { success: 'Sold', pending: 'Selling', failed: 'Sell failed' },
claim: { success: 'Claimed', pending: 'Claiming', failed: 'Claim failed' },
stake: { success: 'Staked', pending: 'Staking', failed: 'Stake failed' },
unstake: {
success: 'Unstaked',
pending: 'Unstaking',
failed: 'Unstake failed',
},
deposit: {
success: 'Deposited',
pending: 'Depositing',
failed: 'Deposit failed',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ import {
// eslint-disable-next-line import-x/no-restricted-paths -- TODO(ADR-0020): route-isolation backlog
import { getPerpsDisplaySymbol } from '@metamask/perps-controller';
import type { ActivityListItemRowContent } from './ActivityListItemRow.types';
import {
ACTIVITY_FALLBACK_TITLE_RESOLVERS,
TOKEN_ACTION_LABELS,
} from './titleLabels';

function isPerpsFundsKind(type: ActivityKind): boolean {
return type === 'perpsAddFunds' || type === 'perpsWithdraw';
Expand Down Expand Up @@ -263,47 +267,6 @@ function statusTitle(
return titles.success;
}

const ACTIVITY_FALLBACK_TITLE_RESOLVERS: Partial<
Record<ActivityKind, () => string>
> = {
predictionsAddFunds: () =>
strings('transactions.activity_prediction_account_funded'),
predictionsWithdrawFunds: () =>
strings('transactions.activity_prediction_withdrawal'),
predictionClaimWinnings: () => strings('predict.transactions.claim_title'),
predictionCashedOut: () => strings('predict.transactions.sell_title'),
// Design board copy: "Prediction placed" (not the legacy "Predicted").
predictionPlaced: () => strings('transactions.activity_prediction_placed'),
perpsAddFunds: () => strings('transactions.activity_perps_account_funded'),
perpsWithdraw: () => strings('transactions.activity_perps_withdrawal'),
perpsOpenLong: () => strings('transactions.activity_perps_open_long'),
perpsCloseLong: () => strings('transactions.activity_perps_close_long'),
perpsCloseLongLiquidated: () =>
strings('transactions.activity_perps_close_long_liquidated'),
perpsCloseLongStopLoss: () =>
strings('transactions.activity_perps_close_long_stop_loss'),
perpsOpenShort: () => strings('transactions.activity_perps_open_short'),
perpsCloseShort: () => strings('transactions.activity_perps_close_short'),
perpsCloseShortLiquidated: () =>
strings('transactions.activity_perps_close_short_liquidated'),
perpsCloseShortStopLoss: () =>
strings('transactions.activity_perps_close_short_stop_loss'),
perpsPaidFundingFees: () =>
strings('transactions.activity_perps_paid_funding_fees'),
perpsReceivedFundingFees: () =>
strings('transactions.activity_perps_received_funding_fees'),
perpsCloseShortTakeProfit: () =>
strings('transactions.activity_perps_close_short_take_profit'),
perpsCloseLongTakeProfit: () =>
strings('transactions.activity_perps_close_long_take_profit'),
marketShort: () => strings('transactions.activity_market_short'),
stopMarketCloseShort: () =>
strings('transactions.activity_stop_market_close_short'),
marketCloseShort: () => strings('transactions.activity_market_close_short'),
limitShort: () => strings('transactions.activity_limit_short'),
limitCloseShort: () => strings('transactions.activity_limit_close_short'),
};

// Domain (perps/predict) rows have no bespoke failed copy, so mark a
// failed/cancelled status with an em-dash "—Failed" suffix, mirroring the perps
// severity suffix style (e.g. "Closed short—liquidated"). The failed color is
Expand Down Expand Up @@ -623,44 +586,27 @@ function resolveCoreContent(
case 'buy':
case 'sell':
case 'claim':
case 'stake':
case 'unstake':
case 'deposit': {
const token = item.data.token;
const symbol = token?.symbol;
const isNamelessNftBuy = item.type === 'buy' && isNamelessNftToken(token);
const labels =
item.type === 'buy'
? { success: 'Bought', pending: 'Buying', failed: 'Buy failed' }
: item.type === 'sell'
? { success: 'Sold', pending: 'Selling', failed: 'Sell failed' }
: item.type === 'claim'
? {
success: 'Claimed',
pending: 'Claiming',
failed: 'Claim failed',
}
: item.type === 'unstake'
? {
success: 'Unstaked',
pending: 'Unstaking',
failed: 'Unstake failed',
}
: {
success: 'Deposited',
pending: 'Depositing',
failed: 'Deposit failed',
};
// Pooled staking is ETH-only, so stake/unstake read the full asset name
// ("Staked Ethereum" / "Unstaked Ethereum") rather than the "ETH" symbol.
const isStakingKind = item.type === 'stake' || item.type === 'unstake';
let displayNoun = symbol;
if (isStakingKind) {
displayNoun = 'Ethereum';
} else if (isNamelessNftBuy) {
displayNoun = 'NFT';
}
const labels = TOKEN_ACTION_LABELS[item.type];

return {
title: statusTitle(item, {
success: withOptionalSymbol(
labels.success,
isNamelessNftBuy ? 'NFT' : symbol,
),
pending: withOptionalSymbol(
labels.pending,
isNamelessNftBuy ? 'NFT' : symbol,
),
success: withOptionalSymbol(labels.success, displayNoun),
pending: withOptionalSymbol(labels.pending, displayNoun),
failed: labels.failed,
}),
subtitle: protocolSubtitle(item),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function DepositDetails({
}: {
item: Extract<
ActivityListItem,
{ type: 'buy' | 'claim' | 'deposit' | 'unstake' }
{ type: 'buy' | 'claim' | 'deposit' | 'stake' | 'unstake' }
>;
}) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,22 @@ const unstakeItem: ActivityListItem = {
},
} as ActivityListItem;

const stakeItem: ActivityListItem = {
type: 'stake',
chainId: 'eip155:1',
status: 'success',
timestamp: 1,
hash: '0xstake',
data: {
token: {
amount: '1000000000000000000',
decimals: 18,
symbol: 'ETH',
direction: 'out',
},
},
} as ActivityListItem;

const smartAccountUpgradeItem: ActivityListItem = {
type: 'smartAccountUpgrade',
chainId: 'eip155:1',
Expand Down Expand Up @@ -433,6 +449,7 @@ describe('TemplateLoader', () => {
['contract interaction', contractItem],
['claim mUSD bonus', claimMusdBonusItem],
['earn/staking deposit', depositItem],
['earn/staking stake', stakeItem],
['earn/staking claim', claimItem],
['earn/staking unstake', unstakeItem],
])('renders the %s details template', (_type, item) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export function TemplateLoader({
case 'claimMusdBonus':
return <ClaimMusdBonusDetails item={item} />;
case 'claim':
case 'stake':
case 'unstake':
return <DepositDetails item={item} />;
case 'smartAccountUpgrade':
Expand Down
13 changes: 13 additions & 0 deletions app/components/Views/ActivityScreen/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ describe('activityKindMatchesTypeFilter', () => {
).toBe(true);
}
});

it('groups earn/staking kinds (incl. stake) under Transactions', () => {
for (const kind of [
'stake',
'deposit',
'claim',
'unstake',
] as ActivityKind[]) {
expect(
activityKindMatchesTypeFilter(kind, ActivityTypeFilter.Transactions),
).toBe(true);
}
});
});

describe('resolveInitialActivityTypeFilter', () => {
Expand Down
1 change: 1 addition & 0 deletions app/components/Views/ActivityScreen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export const ACTIVITY_TYPE_FILTER_KINDS: Record<
'nftMint',
'nftSell',
'deposit',
'stake',
'claim',
'unstake',
'lendingDeposit',
Expand Down
6 changes: 3 additions & 3 deletions app/util/activity-adapters/adapters/local-transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1002,7 +1002,7 @@ describe('mapLocalTransaction', () => {
);
});

it('maps a staking deposit to a deposit activity with the network fee', () => {
it('maps a staking deposit to a stake activity with the network fee', () => {
const transaction = {
chainId: mainnet,
hash: '0xstakedeposit',
Expand All @@ -1015,7 +1015,7 @@ describe('mapLocalTransaction', () => {

expect(mapLocalTransaction(makeGroup(transaction))).toEqual(
expect.objectContaining({
type: 'deposit',
type: 'stake',
data: expect.objectContaining({ fees: expect.any(Array) }),
}),
);
Expand Down Expand Up @@ -1145,7 +1145,7 @@ describe('mapLocalTransaction', () => {
const result = mapLocalTransaction(
makeGroup(transaction, { nativeAssetSymbol: undefined }),
);
expect(result.type).toBe('deposit');
expect(result.type).toBe('stake');
const { token } = result.data as { token?: unknown };
expect(token).toBeUndefined();
});
Expand Down
2 changes: 1 addition & 1 deletion app/util/activity-adapters/adapters/local-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ export function mapLocalTransaction(

case TransactionType.stakingDeposit:
return {
type: 'deposit',
type: 'stake',
chainId,
status,
timestamp,
Expand Down
3 changes: 2 additions & 1 deletion app/util/activity-adapters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type ActivityKind =
| 'sell'
| 'buy'
| 'deposit'
| 'stake'
| 'unstake'
| 'swap'
| 'swapIncomplete'
Expand Down Expand Up @@ -146,7 +147,7 @@ export type ActivityListItem =
}
>
| ActivityData<
'buy' | 'claim' | 'deposit' | 'unstake',
'buy' | 'claim' | 'deposit' | 'stake' | 'unstake',
{
from?: string;
to?: string;
Expand Down
Loading