diff --git a/CLAUDE.md b/CLAUDE.md index 1c415341010..4beb4e217f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,6 +79,13 @@ - Avoid `let` variable assignments - prefer `const` with inline IIFE switch statements or extract to functions for conditional logic - For static JSX icon elements (e.g., ``) that don't depend on state/props, define them as constants outside the component to avoid re-renders instead of using useMemo +### CLI Tool Preferences +- Prefer `rg` (ripgrep) over `grep` for searching - it's faster and respects .gitignore +- Use `jq` for querying and manipulating JSON files instead of reading entire files into context + - Example: `jq '.yieldXYZ | keys' src/assets/translations/en/main.json` to list keys + - Example: `jq '.someKey.nested' file.json` to extract specific values +- This is especially important for large JSON files (like generated data or translation files) to avoid context bloat and improve performance + ### Git & Version Control - Never commit changes unless explicitly requested - When creating commits, follow the Git Safety Protocol (see session notes) @@ -96,6 +103,7 @@ - Add English copy to `src/assets/translations/en/main.json` (find appropriate section) - Ignore other language translation files - only update English - Use the translation hook: `useTranslate()` from `react-polyglot` +- **Both steps required**: Translations must be (1) added to `en/main.json` AND (2) consumed via `translate('key')` - missing either step results in untranslated strings showing raw keys ### Feature Flags - Feature flags are stored in Redux state under `preferences.featureFlags` diff --git a/src/assets/translations/en/main.json b/src/assets/translations/en/main.json index 6250b7e04d9..c324d311341 100644 --- a/src/assets/translations/en/main.json +++ b/src/assets/translations/en/main.json @@ -2672,12 +2672,7 @@ "yieldXYZ": { "pageTitle": "Yields", "pageSubtitle": "Discover and manage yield opportunities across multiple chains", - "enter": "Enter", - "exit": "Exit", - "enterAsset": "Enter %{asset}", - "actions": { - "restake": "Restake" - }, + "actions": {}, "yield": "Yield", "apy": "APY", "apr": "APR", @@ -2689,12 +2684,17 @@ "noYields": "No yield opportunities available", "connectWallet": "Connect a wallet to view yields", "stats": "Stats", - "minEnter": "Min Enter", + "minStake": "Min Stake", + "minDeposit": "Min Deposit", "rewardSchedule": "Reward Schedule", - "gasToken": "Gas Token", - "entering": "Entering...", - "exiting": "Exiting...", - "unstaking": "Unstaking", + "staking": "Staking...", + "unstaking": "Unstaking...", + "depositing": "Depositing...", + "withdrawing": "Withdrawing...", + "stakingPending": "Staking", + "unstakingPending": "Unstaking", + "depositingPending": "Depositing", + "withdrawingPending": "Withdrawing", "availableDate": "available %{date}", "withdrawable": "Withdrawable", "type": "Type", @@ -2707,13 +2707,9 @@ "maxApy": "Max APY", "nativeStaking": "Native Staking", "validator": "Validator", - "entered": "Entered", + "staked": "Staked", "claimable": "Claimable", "loadingQuote": "Loading Quote...", - "selectValidator": "Select Validator", - "allValidators": "All Validators", - "myValidators": "My Validators", - "noValidatorsFound": "No validators found", "preferred": "Preferred", "pending": "Pending", "ready": "Ready", @@ -2729,16 +2725,11 @@ "allTypes": "All Types", "showAll": "Show All", "searchValidator": "Search for validator", - "enterYourToken": "Enter your %{symbol} to start earning yield securely.", - "noActiveValidators": "You don't have any active validators yet.", "success": "Success!", "transactions": "Transactions", "currentApy": "Current APY", "estYearlyEarnings": "Est. Yearly Earnings", - "allPositions": "All Positions", "switch": "Switch", - "enterSymbol": "Enter %{symbol}", - "exitSymbol": "Exit %{symbol}", "claimSymbol": "Claim %{symbol}", "stakeSymbol": "Stake %{symbol}", "unstakeSymbol": "Unstake %{symbol}", @@ -2754,12 +2745,10 @@ "market": "market", "markets": "markets", "protocol": "protocol", - "protocols": "protocols", "chain": "chain", "chains": "chains", "reward": "Reward", "assetYields": "%{asset} Yields", - "opportunitiesAvailable": "%{count} opportunities available", "noYieldsMatchingFilters": "No yields found matching filters.", "activePositions": "Active Positions", "acrossPositions": "Across %{count} positions", @@ -2774,15 +2763,15 @@ "recommendedForYou": "Recommended for you", "earn": "Earn", "myBalance": "My Balance", - "balanceByAccount": "Balance by Account", "providers": "Providers", - "successEnter": "You successfully entered %{amount} %{symbol}", - "successExit": "You successfully exited %{amount} %{symbol}", + "successStaked": "You successfully staked %{amount} %{symbol}", + "successUnstaked": "You successfully unstaked %{amount} %{symbol}", + "successDeposited": "You successfully deposited %{amount} %{symbol}", + "successWithdrawn": "You successfully withdrew %{amount} %{symbol}", "successClaim": "You successfully claimed %{amount} %{symbol}", "viewPosition": "View Position", "via": "via", "resetAllowance": "Reset Allowance", - "transactionNumber": "Transaction %{number}", "loading": { "signInWallet": "Sign in Wallet", "waiting": "Waiting", @@ -2830,10 +2819,7 @@ "otherYields": "Other %{symbol} Yields", "availableToDeposit": "Available to Deposit", "availableToDepositTooltip": "This is the amount of %{symbol} in your wallet that you can deposit into this yield opportunity.", - "potentialEarningsAmount": "%{amount}/yr at %{apy}% APY", - "depositNow": "Deposit Now", - "strategyInfo": "Strategy Info", - "overview": "Overview" + "getAsset": "Get %{symbol}" }, "earn": { "enterFrom": "Enter from", diff --git a/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx b/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx index e9cfbc29688..e82fb192ffa 100644 --- a/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx +++ b/src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx @@ -9,7 +9,7 @@ import { EarnRoutePaths } from './types' import { Amount } from '@/components/Amount/Amount' import { bnOrZero } from '@/lib/bignumber/bignumber' import { DEFAULT_NATIVE_VALIDATOR_BY_CHAIN_ID } from '@/lib/yieldxyz/constants' -import { getTransactionButtonText } from '@/lib/yieldxyz/utils' +import { getTransactionButtonText, getYieldActionLabelKeys } from '@/lib/yieldxyz/utils' import { GradientApy } from '@/pages/Yields/components/GradientApy' import { TransactionStepsList } from '@/pages/Yields/components/TransactionStepsList' import { YieldAssetFlow } from '@/pages/Yields/components/YieldAssetFlow' @@ -157,14 +157,25 @@ export const EarnConfirm = memo(() => { return translate('yieldXYZ.resetAllowance') } // Before execution starts, use the first CREATED transaction from quoteData + const yieldType = selectedYield?.mechanics.type const firstCreatedTx = quoteData?.transactions?.find(tx => tx.status === 'CREATED') if (firstCreatedTx) { - return getTransactionButtonText(firstCreatedTx.type, firstCreatedTx.title) + return getTransactionButtonText(firstCreatedTx.type, firstCreatedTx.title, yieldType) } // Fallback states if (isLoading) return translate('common.loadingText') - return translate('yieldXYZ.enter') - }, [activeStepIndex, transactionSteps, isUsdtResetRequired, quoteData, isLoading, translate]) + if (!yieldType) return translate('common.deposit') + const actionLabelKeys = getYieldActionLabelKeys(yieldType) + return translate(actionLabelKeys.enter) + }, [ + activeStepIndex, + transactionSteps, + isUsdtResetRequired, + quoteData, + isLoading, + translate, + selectedYield?.mechanics.type, + ]) const providerInfo = useMemo(() => { if (selectedValidator) { @@ -297,9 +308,7 @@ export const EarnConfirm = memo(() => { {providerInfo && ( - {selectedValidator - ? translate('yieldXYZ.validator') - : translate('yieldXYZ.provider')} + {translate(selectedValidator ? 'yieldXYZ.validator' : 'yieldXYZ.provider')} diff --git a/src/lib/yieldxyz/types.ts b/src/lib/yieldxyz/types.ts index d4197dc0627..3f00a467e2c 100644 --- a/src/lib/yieldxyz/types.ts +++ b/src/lib/yieldxyz/types.ts @@ -30,6 +30,15 @@ export enum ActionIntent { Manage = 'manage', } +export type YieldType = + | 'staking' + | 'native-staking' + | 'pooled-staking' + | 'liquid-staking' + | 'restaking' + | 'vault' + | 'lending' + export enum ActionStatus { Canceled = 'CANCELED', Created = 'CREATED', @@ -230,7 +239,7 @@ export type YieldEntryLimits = { } export type YieldMechanics = { - type: string + type: YieldType requiresValidatorSelection: boolean rewardSchedule: string rewardClaiming: string diff --git a/src/lib/yieldxyz/utils.test.ts b/src/lib/yieldxyz/utils.test.ts index 1935ac4258c..037b94343e6 100644 --- a/src/lib/yieldxyz/utils.test.ts +++ b/src/lib/yieldxyz/utils.test.ts @@ -4,8 +4,10 @@ import { SHAPESHIFT_COSMOS_VALIDATOR_ADDRESS } from './constants' import type { AugmentedYieldDto, ValidatorDto } from './types' import { ensureValidatorApr, + formatYieldTxTitle, getTransactionButtonText, getYieldActionLabelKeys, + getYieldSuccessMessageKey, isStakingYieldType, resolveYieldInputAssetIcon, searchValidators, @@ -19,10 +21,20 @@ describe('getTransactionButtonText', () => { expect(getTransactionButtonText('claim-rewards', undefined)).toBe('Claim') }) - it('should fallback to parsing title when type is unknown', () => { + it('should use vault terminology (deposit/withdraw) by default', () => { expect(getTransactionButtonText(undefined, 'Approve token')).toBe('Approve') - expect(getTransactionButtonText(undefined, 'Deposit ETH transaction')).toBe('Enter') + expect(getTransactionButtonText(undefined, 'Deposit ETH transaction')).toBe('Deposit') expect(getTransactionButtonText(undefined, 'Claim rewards')).toBe('Claim') + expect(getTransactionButtonText('DEPOSIT', undefined)).toBe('Deposit') + expect(getTransactionButtonText('WITHDRAW', undefined)).toBe('Withdraw') + }) + + it('should use staking terminology when yieldType is staking', () => { + expect(getTransactionButtonText('STAKE', undefined, 'staking')).toBe('Stake') + expect(getTransactionButtonText('UNSTAKE', undefined, 'staking')).toBe('Unstake') + expect(getTransactionButtonText('DEPOSIT', undefined, 'liquid-staking')).toBe('Stake') + expect(getTransactionButtonText('WITHDRAW', undefined, 'liquid-staking')).toBe('Unstake') + expect(getTransactionButtonText(undefined, 'Deposit ETH', 'native-staking')).toBe('Stake') }) it('should return Confirm as final fallback', () => { @@ -266,9 +278,9 @@ describe('getYieldActionLabelKeys', () => { }) }) - it('should return restake/unstake for restaking yield types', () => { + it('should return stake/unstake for restaking yield types', () => { expect(getYieldActionLabelKeys('restaking')).toEqual({ - enter: 'yieldXYZ.actions.restake', + enter: 'defi.stake', exit: 'defi.unstake', }) }) @@ -286,17 +298,6 @@ describe('getYieldActionLabelKeys', () => { exit: 'common.withdraw', }) }) - - it('should return deposit/withdraw for unknown yield types', () => { - expect(getYieldActionLabelKeys('unknown')).toEqual({ - enter: 'common.deposit', - exit: 'common.withdraw', - }) - expect(getYieldActionLabelKeys('')).toEqual({ - enter: 'common.deposit', - exit: 'common.withdraw', - }) - }) }) describe('isStakingYieldType', () => { @@ -311,7 +312,54 @@ describe('isStakingYieldType', () => { it('should return false for non-staking yield types', () => { expect(isStakingYieldType('vault')).toBe(false) expect(isStakingYieldType('lending')).toBe(false) - expect(isStakingYieldType('unknown')).toBe(false) - expect(isStakingYieldType('')).toBe(false) + }) +}) + +describe('formatYieldTxTitle', () => { + it('should use vault terminology by default', () => { + expect(formatYieldTxTitle('Deposit ETH', 'ETH')).toBe('Deposit ETH') + expect(formatYieldTxTitle('Withdraw ETH transaction', 'ETH')).toBe('Withdraw ETH') + expect(formatYieldTxTitle('Approve ETH', 'ETH')).toBe('Approve ETH') + }) + + it('should use staking terminology when yieldType is staking', () => { + expect(formatYieldTxTitle('Deposit ETH', 'ETH', 'staking')).toBe('Stake ETH') + expect(formatYieldTxTitle('Withdraw ETH', 'ETH', 'liquid-staking')).toBe('Unstake ETH') + expect(formatYieldTxTitle('Exit ETH', 'ETH', 'native-staking')).toBe('Unstake ETH') + expect(formatYieldTxTitle('Unstake ETH', 'ETH', 'pooled-staking')).toBe('Unstake ETH') + }) + + it('should preserve unknown titles', () => { + expect(formatYieldTxTitle('Custom action', 'ETH')).toBe('Custom action') + expect(formatYieldTxTitle('Custom action', 'ETH', 'staking')).toBe('Custom action') + }) +}) + +describe('getYieldSuccessMessageKey', () => { + it('should return staking success keys for staking yield types', () => { + expect(getYieldSuccessMessageKey('staking', 'enter')).toBe('successStaked') + expect(getYieldSuccessMessageKey('staking', 'exit')).toBe('successUnstaked') + expect(getYieldSuccessMessageKey('native-staking', 'enter')).toBe('successStaked') + expect(getYieldSuccessMessageKey('liquid-staking', 'exit')).toBe('successUnstaked') + expect(getYieldSuccessMessageKey('pooled-staking', 'enter')).toBe('successStaked') + }) + + it('should return staking success key for restaking yield types', () => { + expect(getYieldSuccessMessageKey('restaking', 'enter')).toBe('successStaked') + expect(getYieldSuccessMessageKey('restaking', 'exit')).toBe('successUnstaked') + }) + + it('should return vault success keys for vault/lending yield types', () => { + expect(getYieldSuccessMessageKey('vault', 'enter')).toBe('successDeposited') + expect(getYieldSuccessMessageKey('vault', 'exit')).toBe('successWithdrawn') + expect(getYieldSuccessMessageKey('lending', 'enter')).toBe('successDeposited') + expect(getYieldSuccessMessageKey('lending', 'exit')).toBe('successWithdrawn') + }) + + it('should return successClaim for claim and manage actions', () => { + expect(getYieldSuccessMessageKey('staking', 'claim')).toBe('successClaim') + expect(getYieldSuccessMessageKey('vault', 'claim')).toBe('successClaim') + expect(getYieldSuccessMessageKey('staking', 'manage')).toBe('successClaim') + expect(getYieldSuccessMessageKey('vault', 'manage')).toBe('successClaim') }) }) diff --git a/src/lib/yieldxyz/utils.ts b/src/lib/yieldxyz/utils.ts index 4e02ee855a6..ec55769eae3 100644 --- a/src/lib/yieldxyz/utils.ts +++ b/src/lib/yieldxyz/utils.ts @@ -6,76 +6,110 @@ import { SHAPESHIFT_COSMOS_VALIDATOR_ADDRESS, YIELD_NETWORK_TO_CHAIN_ID, } from './constants' -import type { AugmentedYieldDto, ValidatorDto, YieldIconSource } from './types' +import type { AugmentedYieldDto, ValidatorDto, YieldIconSource, YieldType } from './types' export const yieldNetworkToChainId = (network: string): ChainId | undefined => { if (!isSupportedYieldNetwork(network)) return undefined return YIELD_NETWORK_TO_CHAIN_ID[network] } -const TX_TITLE_PATTERNS: [RegExp, string][] = [ - [/approv/i, 'Approve'], - [/supply|deposit|enter/i, 'Enter'], - [/withdraw|exit|unstake|undelegate/i, 'Exit'], - [/claim/i, 'Claim'], - [/stake|delegate/i, 'Enter'], - [/bridge/i, 'Bridge'], - [/swap/i, 'Swap'], +type TxTitlePattern = { + pattern: RegExp + staking: string + vault: string +} + +const TX_TITLE_PATTERNS: TxTitlePattern[] = [ + { pattern: /approv/i, staking: 'Approve', vault: 'Approve' }, + { pattern: /supply|deposit|enter/i, staking: 'Stake', vault: 'Deposit' }, + { pattern: /withdraw|exit/i, staking: 'Unstake', vault: 'Withdraw' }, + { pattern: /unstake|undelegate/i, staking: 'Unstake', vault: 'Withdraw' }, + { pattern: /claim/i, staking: 'Claim', vault: 'Claim' }, + { pattern: /stake|delegate/i, staking: 'Stake', vault: 'Deposit' }, + { pattern: /bridge/i, staking: 'Bridge', vault: 'Bridge' }, + { pattern: /swap/i, staking: 'Swap', vault: 'Swap' }, ] -// Map of transaction types to user-friendly button labels -// These should match the action verbs shown in the step row (without the asset symbol) -// Yield.xyz uses Enter/Exit terminology consistently -const TX_TYPE_TO_LABEL: Record = { - APPROVE: 'Approve', - APPROVAL: 'Approve', - DELEGATE: 'Enter', // Monad uses DELEGATE for staking - UNDELEGATE: 'Exit', // Monad uses UNDELEGATE for unstaking - STAKE: 'Enter', - UNSTAKE: 'Exit', - DEPOSIT: 'Enter', - WITHDRAW: 'Exit', - SUPPLY: 'Enter', - EXIT: 'Exit', - ENTER: 'Enter', - BRIDGE: 'Bridge', - SWAP: 'Swap', - CLAIM: 'Claim', - CLAIM_REWARDS: 'Claim', - TRANSFER: 'Transfer', +type TxTypeLabels = { + staking: string + vault: string +} + +const TX_TYPE_TO_LABELS: Record = { + APPROVE: { staking: 'Approve', vault: 'Approve' }, + APPROVAL: { staking: 'Approve', vault: 'Approve' }, + DELEGATE: { staking: 'Stake', vault: 'Deposit' }, + UNDELEGATE: { staking: 'Unstake', vault: 'Withdraw' }, + STAKE: { staking: 'Stake', vault: 'Deposit' }, + UNSTAKE: { staking: 'Unstake', vault: 'Withdraw' }, + DEPOSIT: { staking: 'Stake', vault: 'Deposit' }, + WITHDRAW: { staking: 'Unstake', vault: 'Withdraw' }, + SUPPLY: { staking: 'Stake', vault: 'Deposit' }, + EXIT: { staking: 'Unstake', vault: 'Withdraw' }, + ENTER: { staking: 'Stake', vault: 'Deposit' }, + BRIDGE: { staking: 'Bridge', vault: 'Bridge' }, + SWAP: { staking: 'Swap', vault: 'Swap' }, + CLAIM: { staking: 'Claim', vault: 'Claim' }, + CLAIM_REWARDS: { staking: 'Claim', vault: 'Claim' }, + TRANSFER: { staking: 'Transfer', vault: 'Transfer' }, +} + +type TerminologyKey = 'staking' | 'vault' + +const isStakingType = (yieldType: YieldType): boolean => { + switch (yieldType) { + case 'staking': + case 'native-staking': + case 'pooled-staking': + case 'liquid-staking': + case 'restaking': + return true + case 'vault': + case 'lending': + return false + default: + // This shouldn't happen but satisfies exhaustiveness check + assertNever(yieldType) + return false + } } /** * Gets a clean button label from a transaction type or title. - * Used for the main CTA button in the yield action modal. + * Uses yield-type-aware terminology (stake/unstake vs deposit/withdraw). */ export const getTransactionButtonText = ( type: string | undefined, title: string | undefined, + yieldType?: YieldType, ): string => { - // First try to use the transaction type directly + const labelKey: TerminologyKey = yieldType && isStakingType(yieldType) ? 'staking' : 'vault' + if (type) { const normalized = type.toUpperCase().replace(/[_-]/g, '_') - if (TX_TYPE_TO_LABEL[normalized]) { - return TX_TYPE_TO_LABEL[normalized] - } - // Fallback: capitalize the type + const labels = TX_TYPE_TO_LABELS[normalized] + if (labels) return labels[labelKey] return type.charAt(0).toUpperCase() + type.slice(1).toLowerCase() } - // Fall back to parsing the title if (title) { - const match = TX_TITLE_PATTERNS.find(([pattern]) => pattern.test(title)) - if (match) return match[1] + const match = TX_TITLE_PATTERNS.find(p => p.pattern.test(title)) + if (match) return match[labelKey] } return 'Confirm' } -export const formatYieldTxTitle = (title: string, assetSymbol: string): string => { +export const formatYieldTxTitle = ( + title: string, + assetSymbol: string, + yieldType?: YieldType, +): string => { + const labelKey: TerminologyKey = yieldType && isStakingType(yieldType) ? 'staking' : 'vault' + const normalized = title.replace(/ transaction$/i, '').toLowerCase() - const match = TX_TITLE_PATTERNS.find(([pattern]) => pattern.test(normalized)) - if (match) return `${match[1]} ${assetSymbol}` + const match = TX_TITLE_PATTERNS.find(p => p.pattern.test(normalized)) + if (match) return `${match[labelKey]} ${assetSymbol}` return normalized.charAt(0).toUpperCase() + normalized.slice(1) } @@ -166,41 +200,140 @@ export type YieldActionLabelKeys = { exit: string } +const assertNever = (value: never): never => { + throw new Error(`Unhandled yield type: ${value}`) +} + /** * Gets the appropriate translation keys for yield actions based on yield type. * * Yield types and their terminology: - * - staking, native-staking, pooled-staking, liquid-staking → Stake/Unstake - * - restaking → Restake/Unstake - * - vault, lending, and others → Deposit/Withdraw + * - staking, native-staking, pooled-staking, liquid-staking, restaking → Stake/Unstake + * - vault, lending → Deposit/Withdraw */ -export const getYieldActionLabelKeys = (yieldType: string): YieldActionLabelKeys => { +export const getYieldActionLabelKeys = (yieldType: YieldType): YieldActionLabelKeys => { switch (yieldType) { case 'staking': case 'native-staking': case 'pooled-staking': case 'liquid-staking': + case 'restaking': return { enter: 'defi.stake', exit: 'defi.unstake' } + case 'vault': + case 'lending': + return { enter: 'common.deposit', exit: 'common.withdraw' } + default: + return assertNever(yieldType) + } +} + +export type YieldLoadingStateKeys = { + enter: string + exit: string +} + +export const getYieldLoadingStateKeys = (yieldType: YieldType): YieldLoadingStateKeys => { + switch (yieldType) { + case 'staking': + case 'native-staking': + case 'pooled-staking': + case 'liquid-staking': case 'restaking': - return { enter: 'yieldXYZ.actions.restake', exit: 'defi.unstake' } + return { enter: 'yieldXYZ.staking', exit: 'yieldXYZ.unstaking' } case 'vault': case 'lending': + return { enter: 'yieldXYZ.depositing', exit: 'yieldXYZ.withdrawing' } default: - return { enter: 'common.deposit', exit: 'common.withdraw' } + return assertNever(yieldType) } } -const STAKING_YIELD_TYPES = new Set([ - 'staking', - 'native-staking', - 'pooled-staking', - 'liquid-staking', - 'restaking', -]) +export type YieldHeadingKeys = { + enter: string + exit: string +} -/** - * Checks if a yield type uses staking terminology (stake/unstake). - */ -export const isStakingYieldType = (yieldType: string): boolean => { - return STAKING_YIELD_TYPES.has(yieldType) +export const getYieldHeadingKeys = (yieldType: YieldType): YieldHeadingKeys => { + switch (yieldType) { + case 'staking': + case 'native-staking': + case 'pooled-staking': + case 'liquid-staking': + case 'restaking': + return { enter: 'yieldXYZ.stakeSymbol', exit: 'yieldXYZ.unstakeSymbol' } + case 'vault': + case 'lending': + return { enter: 'yieldXYZ.depositSymbol', exit: 'yieldXYZ.withdrawSymbol' } + default: + return assertNever(yieldType) + } +} + +export type YieldPendingStatusKeys = { + enter: string + exit: string +} + +export const getYieldPendingStatusKeys = (yieldType: YieldType): YieldPendingStatusKeys => { + switch (yieldType) { + case 'staking': + case 'native-staking': + case 'pooled-staking': + case 'liquid-staking': + case 'restaking': + return { enter: 'yieldXYZ.stakingPending', exit: 'yieldXYZ.unstakingPending' } + case 'vault': + case 'lending': + return { enter: 'yieldXYZ.depositingPending', exit: 'yieldXYZ.withdrawingPending' } + default: + return assertNever(yieldType) + } +} + +export const getYieldMinAmountKey = (yieldType: YieldType): string => { + switch (yieldType) { + case 'staking': + case 'native-staking': + case 'pooled-staking': + case 'liquid-staking': + case 'restaking': + return 'yieldXYZ.minStake' + case 'vault': + case 'lending': + return 'yieldXYZ.minDeposit' + default: + return assertNever(yieldType) + } +} + +export const isStakingYieldType = (yieldType: YieldType): boolean => { + return isStakingType(yieldType) +} + +export type YieldSuccessMessageKey = + | 'successStaked' + | 'successUnstaked' + | 'successDeposited' + | 'successWithdrawn' + | 'successClaim' + +export const getYieldSuccessMessageKey = ( + yieldType: YieldType, + action: 'enter' | 'exit' | 'claim' | 'manage', +): YieldSuccessMessageKey => { + if (action === 'claim' || action === 'manage') return 'successClaim' + + switch (yieldType) { + case 'staking': + case 'native-staking': + case 'pooled-staking': + case 'liquid-staking': + case 'restaking': + return action === 'enter' ? 'successStaked' : 'successUnstaked' + case 'vault': + case 'lending': + return action === 'enter' ? 'successDeposited' : 'successWithdrawn' + default: + return assertNever(yieldType) + } } diff --git a/src/pages/Yields/components/YieldActionModal.tsx b/src/pages/Yields/components/YieldActionModal.tsx index e29bfc1d100..219afc0aff5 100644 --- a/src/pages/Yields/components/YieldActionModal.tsx +++ b/src/pages/Yields/components/YieldActionModal.tsx @@ -16,7 +16,14 @@ import { SHAPESHIFT_VALIDATOR_NAME, } from '@/lib/yieldxyz/constants' import type { AugmentedYieldDto } from '@/lib/yieldxyz/types' -import { getTransactionButtonText, isStakingYieldType } from '@/lib/yieldxyz/utils' +import { + getTransactionButtonText, + getYieldActionLabelKeys, + getYieldHeadingKeys, + getYieldLoadingStateKeys, + getYieldSuccessMessageKey, + isStakingYieldType, +} from '@/lib/yieldxyz/utils' import { GradientApy } from '@/pages/Yields/components/GradientApy' import { TransactionStepsList } from '@/pages/Yields/components/TransactionStepsList' import { YieldAssetFlow } from '@/pages/Yields/components/YieldAssetFlow' @@ -132,13 +139,10 @@ export const YieldActionModal = memo(function YieldActionModal({ providers, ]) - const chainId = useMemo(() => yieldItem.chainId ?? '', [yieldItem.chainId]) + const chainId = yieldItem.chainId ?? '' const feeAsset = useAppSelector(state => selectFeeAssetByChainId(state, chainId)) - const assetAvatarSrc = useMemo( - () => assetLogoURI ?? yieldItem.token.logoURI, - [assetLogoURI, yieldItem.token.logoURI], - ) + const assetAvatarSrc = assetLogoURI ?? yieldItem.token.logoURI const aprFormatted = useMemo( () => `${bnOrZero(yieldItem.rewardRate.total).times(100).toFixed(2)}%`, @@ -185,16 +189,25 @@ export const YieldActionModal = memo(function YieldActionModal({ if (activeStepIndex >= 0 && transactionSteps[activeStepIndex]?.loadingMessage) { return transactionSteps[activeStepIndex].loadingMessage } - if (action === 'enter') return translate('yieldXYZ.entering') - if (action === 'exit') return translate('yieldXYZ.exiting') + const loadingKeys = getYieldLoadingStateKeys(yieldItem.mechanics.type) + if (action === 'enter') return translate(loadingKeys.enter) + if (action === 'exit') return translate(loadingKeys.exit) return translate('common.claiming') - }, [isQuoteLoading, action, translate, activeStepIndex, transactionSteps]) + }, [ + isQuoteLoading, + action, + translate, + activeStepIndex, + transactionSteps, + yieldItem.mechanics.type, + ]) const buttonText = useMemo(() => { - // Use the current step's type/title for a clean button label (e.g., "Enter", "Exit", "Approve") + const yieldType = yieldItem.mechanics.type + // Use the current step's type/title for a clean button label (e.g., "Stake", "Unstake", "Approve") if (activeStepIndex >= 0 && transactionSteps[activeStepIndex]) { const step = transactionSteps[activeStepIndex] - return getTransactionButtonText(step.type, step.originalTitle) + return getTransactionButtonText(step.type, step.originalTitle, yieldType) } // USDT reset required before other transactions if (isUsdtResetRequired) { @@ -203,24 +216,31 @@ export const YieldActionModal = memo(function YieldActionModal({ // Before execution starts, use the first CREATED transaction from quoteData const firstCreatedTx = quoteData?.transactions?.find(tx => tx.status === 'CREATED') if (firstCreatedTx) { - return getTransactionButtonText(firstCreatedTx.type, firstCreatedTx.title) + return getTransactionButtonText(firstCreatedTx.type, firstCreatedTx.title, yieldType) } - // Fallback to action-based text - if (action === 'enter') return translate('yieldXYZ.enter') - if (action === 'exit') return translate('yieldXYZ.exit') + // Fallback to action-based text using yield-type-aware labels + const actionLabelKeys = getYieldActionLabelKeys(yieldType) + if (action === 'enter') return translate(actionLabelKeys.enter) + if (action === 'exit') return translate(actionLabelKeys.exit) return translate('common.claim') - }, [action, translate, activeStepIndex, transactionSteps, quoteData, isUsdtResetRequired]) + }, [ + action, + translate, + activeStepIndex, + transactionSteps, + quoteData, + isUsdtResetRequired, + yieldItem.mechanics.type, + ]) const modalHeading = useMemo(() => { - if (action === 'enter') return translate('yieldXYZ.enterSymbol', { symbol: assetSymbol }) - if (action === 'exit') return translate('yieldXYZ.exitSymbol', { symbol: assetSymbol }) + const headingKeys = getYieldHeadingKeys(yieldItem.mechanics.type) + if (action === 'enter') return translate(headingKeys.enter, { symbol: assetSymbol }) + if (action === 'exit') return translate(headingKeys.exit, { symbol: assetSymbol }) return translate('yieldXYZ.claimSymbol', { symbol: assetSymbol }) - }, [action, assetSymbol, translate]) + }, [action, assetSymbol, translate, yieldItem.mechanics.type]) - const networkAvatarSrc = useMemo( - () => feeAsset?.networkIcon ?? feeAsset?.icon, - [feeAsset?.networkIcon, feeAsset?.icon], - ) + const networkAvatarSrc = feeAsset?.networkIcon ?? feeAsset?.icon const assetFlowDirection = action === 'exit' ? 'exit' : 'enter' @@ -279,32 +299,17 @@ export const YieldActionModal = memo(function YieldActionModal({ )} )} - {showValidatorRow && ( - - - {translate('yieldXYZ.validator')} - - - - - {vaultMetadata.name} - - - - )} - {!showValidatorRow && ( - - - {translate('yieldXYZ.provider')} + + + {translate(showValidatorRow ? 'yieldXYZ.validator' : 'yieldXYZ.provider')} + + + + + {vaultMetadata.name} - - - - {vaultMetadata.name} - - - )} + {translate('yieldXYZ.network')} @@ -347,11 +352,10 @@ export const YieldActionModal = memo(function YieldActionModal({ [animatedAvatarRow, statsContent, displaySteps], ) - const successMessageKey = useMemo(() => { - if (action === 'enter') return 'successEnter' as const - if (action === 'exit') return 'successExit' as const - return 'successClaim' as const - }, [action]) + const successMessageKey = useMemo( + () => getYieldSuccessMessageKey(yieldItem.mechanics.type, action), + [yieldItem.mechanics.type, action], + ) const successProviderInfo = useMemo( () => (vaultMetadata ? { name: vaultMetadata.name, logoURI: vaultMetadata.logoURI } : null), diff --git a/src/pages/Yields/components/YieldAvailableToDeposit.tsx b/src/pages/Yields/components/YieldAvailableToDeposit.tsx index cf998872508..fe0b5675e47 100644 --- a/src/pages/Yields/components/YieldAvailableToDeposit.tsx +++ b/src/pages/Yields/components/YieldAvailableToDeposit.tsx @@ -1,11 +1,27 @@ import { InfoOutlineIcon } from '@chakra-ui/icons' -import { Box, Card, CardBody, Flex, Heading, HStack, Text, Tooltip, VStack } from '@chakra-ui/react' -import { memo, useMemo } from 'react' +import { + Box, + Button, + Card, + CardBody, + Flex, + Heading, + HStack, + Text, + Tooltip, + VStack, +} from '@chakra-ui/react' +import { memo, useCallback, useMemo } from 'react' import { useTranslate } from 'react-polyglot' import { Amount } from '@/components/Amount/Amount' +import { useTradeNavigation } from '@/components/MultiHopTrade/hooks/useTradeNavigation' +import { KeyManager } from '@/context/WalletProvider/KeyManager' +import { useFeatureFlag } from '@/hooks/useFeatureFlag/useFeatureFlag' +import { useWallet } from '@/hooks/useWallet/useWallet' import { bnOrZero } from '@/lib/bignumber/bignumber' import type { AugmentedYieldDto } from '@/lib/yieldxyz/types' +import { selectWalletType } from '@/state/slices/localWalletSlice/selectors' import { selectPortfolioCryptoBalanceBaseUnitByFilter } from '@/state/slices/selectors' import { useAppSelector } from '@/state/store' @@ -17,6 +33,19 @@ type YieldAvailableToDepositProps = { export const YieldAvailableToDeposit = memo( ({ yieldItem, inputTokenMarketData }: YieldAvailableToDepositProps) => { const translate = useTranslate() + const { navigateToTrade } = useTradeNavigation() + const { + state: { isConnected }, + } = useWallet() + const isLedgerReadOnlyEnabled = useFeatureFlag('LedgerReadOnly') + const walletType = useAppSelector(selectWalletType) + const isLedgerReadOnly = isLedgerReadOnlyEnabled && walletType === KeyManager.Ledger + + // Either wallet is physically connected, or it's a Ledger in read-only mode + const hasWallet = useMemo( + () => isConnected || isLedgerReadOnly, + [isConnected, isLedgerReadOnly], + ) const inputToken = yieldItem.inputTokens[0] const inputTokenAssetId = inputToken?.assetId ?? '' @@ -46,13 +75,63 @@ export const YieldAvailableToDeposit = memo( const hasAvailableBalance = availableBalance.gt(0) - if (!inputTokenPrecision) return null + const handleGetAsset = useCallback(() => { + navigateToTrade(inputTokenAssetId) + }, [navigateToTrade, inputTokenAssetId]) + + if (!inputTokenPrecision || !hasWallet) return null const tooltipLabel = translate('yieldXYZ.availableToDepositTooltip', { symbol: yieldItem.token.symbol, }) - if (!hasAvailableBalance) return null + if (!hasAvailableBalance) { + return ( + + + + + + + {translate('yieldXYZ.availableToDeposit')} + + + + + + + + + + + + + + + + + + + + + ) + } return ( diff --git a/src/pages/Yields/components/YieldEnterModal.tsx b/src/pages/Yields/components/YieldEnterModal.tsx index 3ea4266d513..5ac534b1401 100644 --- a/src/pages/Yields/components/YieldEnterModal.tsx +++ b/src/pages/Yields/components/YieldEnterModal.tsx @@ -29,7 +29,14 @@ import { SHAPESHIFT_VALIDATOR_NAME, } from '@/lib/yieldxyz/constants' import type { AugmentedYieldDto } from '@/lib/yieldxyz/types' -import { getTransactionButtonText, isStakingYieldType } from '@/lib/yieldxyz/utils' +import { + getTransactionButtonText, + getYieldActionLabelKeys, + getYieldHeadingKeys, + getYieldMinAmountKey, + getYieldSuccessMessageKey, + isStakingYieldType, +} from '@/lib/yieldxyz/utils' import { GradientApy } from '@/pages/Yields/components/GradientApy' import { TransactionStepsList } from '@/pages/Yields/components/TransactionStepsList' import { YieldExplainers } from '@/pages/Yields/components/YieldExplainers' @@ -357,12 +364,21 @@ export const YieldEnterModal = memo( if (isSubmitting && transactionSteps.length > 0) { const activeStep = transactionSteps.find(s => s.status !== 'success') - if (activeStep) return getTransactionButtonText(activeStep.type, activeStep.originalTitle) + if (activeStep) + return getTransactionButtonText( + activeStep.type, + activeStep.originalTitle, + yieldItem.mechanics.type, + ) } if (activeStepIndex >= 0 && transactionSteps[activeStepIndex]) { const currentStep = transactionSteps[activeStepIndex] - return getTransactionButtonText(currentStep.type, currentStep.originalTitle) + return getTransactionButtonText( + currentStep.type, + currentStep.originalTitle, + yieldItem.mechanics.type, + ) } if (isUsdtResetRequired) { @@ -370,9 +386,15 @@ export const YieldEnterModal = memo( } const firstCreatedTx = quoteData?.transactions?.find(tx => tx.status === 'CREATED') - if (firstCreatedTx) return getTransactionButtonText(firstCreatedTx.type, firstCreatedTx.title) - - return translate('yieldXYZ.enterAsset', { asset: inputTokenAsset?.symbol }) + if (firstCreatedTx) + return getTransactionButtonText( + firstCreatedTx.type, + firstCreatedTx.title, + yieldItem.mechanics.type, + ) + + const actionLabelKeys = getYieldActionLabelKeys(yieldItem.mechanics.type) + return `${translate(actionLabelKeys.enter)} ${inputTokenAsset?.symbol ?? ''}` }, [ isConnected, isQuoteActive, @@ -383,12 +405,14 @@ export const YieldEnterModal = memo( quoteData, translate, inputTokenAsset?.symbol, + yieldItem.mechanics.type, ]) + const headingKeys = getYieldHeadingKeys(yieldItem.mechanics.type) const modalTitle = useMemo(() => { if (step === ModalStep.Success) return translate('common.success') - return translate('yieldXYZ.enterAsset', { asset: inputTokenAsset?.symbol }) - }, [translate, inputTokenAsset?.symbol, step]) + return translate(headingKeys.enter, { symbol: inputTokenAsset?.symbol }) + }, [translate, inputTokenAsset?.symbol, step, headingKeys.enter]) const percentButtons = useMemo( () => ( @@ -482,7 +506,7 @@ export const YieldEnterModal = memo( {minDeposit && bnOrZero(minDeposit).gt(0) && ( - {translate('yieldXYZ.minEnter')} + {translate(getYieldMinAmountKey(yieldItem.mechanics.type))} ( ), [ @@ -598,6 +625,7 @@ export const YieldEnterModal = memo( yieldItem.id, accountId, hookHandleClose, + successMessageKey, ], ) diff --git a/src/pages/Yields/components/YieldForm.tsx b/src/pages/Yields/components/YieldForm.tsx index ead0f0557cf..33019235efc 100644 --- a/src/pages/Yields/components/YieldForm.tsx +++ b/src/pages/Yields/components/YieldForm.tsx @@ -27,6 +27,8 @@ import { YieldBalanceType } from '@/lib/yieldxyz/types' import { getTransactionButtonText, getYieldActionLabelKeys, + getYieldMinAmountKey, + getYieldSuccessMessageKey, isStakingYieldType, } from '@/lib/yieldxyz/utils' import { GradientApy } from '@/pages/Yields/components/GradientApy' @@ -433,12 +435,21 @@ export const YieldForm = memo( if (isSubmitting && transactionSteps.length > 0) { const activeStep = transactionSteps.find(s => s.status !== 'success') - if (activeStep) return getTransactionButtonText(activeStep.type, activeStep.originalTitle) + if (activeStep) + return getTransactionButtonText( + activeStep.type, + activeStep.originalTitle, + yieldItem.mechanics.type, + ) } if (activeStepIndex >= 0 && transactionSteps[activeStepIndex]) { const currentStep = transactionSteps[activeStepIndex] - return getTransactionButtonText(currentStep.type, currentStep.originalTitle) + return getTransactionButtonText( + currentStep.type, + currentStep.originalTitle, + yieldItem.mechanics.type, + ) } if (isUsdtResetRequired) { @@ -446,7 +457,12 @@ export const YieldForm = memo( } const firstCreatedTx = quoteData?.transactions?.find(tx => tx.status === 'CREATED') - if (firstCreatedTx) return getTransactionButtonText(firstCreatedTx.type, firstCreatedTx.title) + if (firstCreatedTx) + return getTransactionButtonText( + firstCreatedTx.type, + firstCreatedTx.title, + yieldItem.mechanics.type, + ) const actionLabelKeys = getYieldActionLabelKeys(yieldItem.mechanics.type) if (action === 'enter') { @@ -566,7 +582,7 @@ export const YieldForm = memo( {minDeposit && bnOrZero(minDeposit).gt(0) && action === 'enter' && ( - {translate('yieldXYZ.minEnter')} + {translate(getYieldMinAmountKey(yieldItem.mechanics.type))} walletDispatch({ type: WalletActions.SET_WALLET_MODAL, payload: true }), + [walletDispatch], + ) const { chainId } = yieldItem const { accountId: contextAccountId, accountNumber } = useYieldAccount() @@ -201,6 +209,7 @@ export const YieldPositionCard = memo( const handleExit = useCallback(() => navigateToAction('exit'), [navigateToAction]) const actionLabelKeys = getYieldActionLabelKeys(yieldItem.mechanics.type) + const pendingStatusKeys = getYieldPendingStatusKeys(yieldItem.mechanics.type) const enterLabel = translate(actionLabelKeys.enter) const exitLabel = translate(actionLabelKeys.exit) @@ -213,7 +222,7 @@ export const YieldPositionCard = memo( - {translate('yieldXYZ.entering')} + {translate(pendingStatusKeys.enter)} {formatBalance(enteringBalance)} @@ -225,7 +234,7 @@ export const YieldPositionCard = memo( ) - }, [hasEntering, translate, formatBalance, enteringBalance]) + }, [hasEntering, translate, formatBalance, enteringBalance, pendingStatusKeys.enter]) const unstakingSection = useMemo(() => { if (!hasExiting) return null @@ -245,7 +254,7 @@ export const YieldPositionCard = memo( - {translate('yieldXYZ.unstaking')} + {translate(pendingStatusKeys.exit)} @@ -265,7 +274,7 @@ export const YieldPositionCard = memo( ) }) - }, [hasExiting, exitingEntries, translate]) + }, [hasExiting, exitingEntries, translate, pendingStatusKeys.exit]) const withdrawableSection = useMemo(() => { if (!hasWithdrawable) return null @@ -360,7 +369,49 @@ export const YieldPositionCard = memo( claimableSection, ]) - if (!accountId) return null + if (!accountId) { + return ( + + + + + {translate('yieldXYZ.myPosition')} + + + + + + {translate('yieldXYZ.totalValue')} + + + + + + + + + + + + + ) + } if (isBalancesLoading) { return ( diff --git a/src/pages/Yields/components/YieldSuccess.tsx b/src/pages/Yields/components/YieldSuccess.tsx index 578a2837473..3a1cd81fe92 100644 --- a/src/pages/Yields/components/YieldSuccess.tsx +++ b/src/pages/Yields/components/YieldSuccess.tsx @@ -10,6 +10,8 @@ import { useConfetti } from '../hooks/useConfetti' import type { TransactionStep } from '../hooks/useYieldTransactionFlow' import { TransactionStepsList } from './TransactionStepsList' +import type { YieldSuccessMessageKey } from '@/lib/yieldxyz/utils' + type ProviderInfo = { name: string logoURI: string | undefined @@ -24,7 +26,7 @@ type YieldSuccessProps = { accountId?: AccountId onDone: () => void showConfetti?: boolean - successMessageKey?: 'successEnter' | 'successExit' | 'successClaim' + successMessageKey?: YieldSuccessMessageKey } export const YieldSuccess = memo( @@ -37,7 +39,7 @@ export const YieldSuccess = memo( accountId, onDone, showConfetti = true, - successMessageKey = 'successEnter', + successMessageKey = 'successStaked', }: YieldSuccessProps) => { const translate = useTranslate() const navigate = useNavigate() diff --git a/src/pages/Yields/hooks/useYieldTransactionFlow.ts b/src/pages/Yields/hooks/useYieldTransactionFlow.ts index ac0c2266c4c..624c02092fc 100644 --- a/src/pages/Yields/hooks/useYieldTransactionFlow.ts +++ b/src/pages/Yields/hooks/useYieldTransactionFlow.ts @@ -186,7 +186,7 @@ export const useYieldTransactionFlow = ({ const yieldChainId = yieldItem?.chainId const { accountId: contextAccountId, accountNumber: contextAccountNumber } = useYieldAccount() - const derivedAccountId = useAppSelector(state => { + const accountId = useAppSelector(state => { if (accountIdProp) return accountIdProp if (contextAccountId) return contextAccountId if (!yieldChainId) return undefined @@ -194,8 +194,6 @@ export const useYieldTransactionFlow = ({ return selectAccountIdByAccountNumberAndChainId(state)[contextAccountNumber]?.[yieldChainId] }) - const accountId = derivedAccountId - const feeAsset = useAppSelector(state => yieldChainId ? selectFeeAssetByChainId(state, yieldChainId) : undefined, ) @@ -221,12 +219,12 @@ export const useYieldTransactionFlow = ({ if (!yieldItem || !userAddress || !yieldChainId) return null if (action !== 'manage' && !amount) return null - const fields = - action === 'enter' - ? yieldItem.mechanics.arguments.enter.fields - : action === 'exit' - ? yieldItem.mechanics.arguments.exit.fields - : [] + const getFields = () => { + if (action === 'enter') return yieldItem.mechanics.arguments.enter.fields + if (action === 'exit') return yieldItem.mechanics.arguments.exit.fields + return [] + } + const fields = getFields() const fieldNames = new Set(fields.map(field => field.name)) const args: Record = {} @@ -356,7 +354,11 @@ export const useYieldTransactionFlow = ({ ...quoteData.transactions .filter(tx => tx.status === TransactionStatus.Created) .map((tx, i) => ({ - title: formatYieldTxTitle(tx.title || `Transaction ${i + 1}`, assetSymbol), + title: formatYieldTxTitle( + tx.title || `Transaction ${i + 1}`, + assetSymbol, + yieldItem?.mechanics.type, + ), originalTitle: tx.title || '', type: tx.type, status: 'pending' as const, @@ -369,6 +371,7 @@ export const useYieldTransactionFlow = ({ transactionSteps, quoteData, assetSymbol, + yieldItem?.mechanics.type, isAllowanceCheckPending, isUsdtResetRequired, translate, @@ -400,19 +403,27 @@ export const useYieldTransactionFlow = ({ } const isApproval = tx.title?.toLowerCase().includes('approv') - const actionType = isApproval - ? ActionType.Approve - : action === 'enter' - ? ActionType.Deposit - : action === 'exit' - ? ActionType.Withdraw - : ActionType.Claim - - const displayType = isApproval - ? GenericTransactionDisplayType.Approve - : action === 'manage' - ? GenericTransactionDisplayType.Claim - : GenericTransactionDisplayType.Yield + + type GenericActionType = + | typeof ActionType.Approve + | typeof ActionType.Deposit + | typeof ActionType.Withdraw + | typeof ActionType.Claim + + const getActionType = (): GenericActionType => { + if (isApproval) return ActionType.Approve + if (action === 'enter') return ActionType.Deposit + if (action === 'exit') return ActionType.Withdraw + return ActionType.Claim + } + const actionType = getActionType() + + const getDisplayType = (): GenericTransactionDisplayType => { + if (isApproval) return GenericTransactionDisplayType.Approve + if (action === 'manage') return GenericTransactionDisplayType.Claim + return GenericTransactionDisplayType.Yield + } + const displayType = getDisplayType() // TODO(gomes): handle claim notifications - there's more logic TBD here (e.g. unbonding periods). // For now, KISS and simply don't handle claims in action center. @@ -439,7 +450,7 @@ export const useYieldTransactionFlow = ({ accountId, message: typeMessagesMap[actionType] ?? - formatYieldTxTitle(tx.title || 'Transaction', assetSymbol), + formatYieldTxTitle(tx.title || 'Transaction', assetSymbol, yieldItem.mechanics.type), amountCryptoPrecision: amount, contractName: yieldItem.metadata.name, chainName: yieldItem.network, @@ -759,7 +770,11 @@ export const useYieldTransactionFlow = ({ } steps.push( ...transactions.map((tx, i) => ({ - title: formatYieldTxTitle(tx.title || `Transaction ${i + 1}`, assetSymbol), + title: formatYieldTxTitle( + tx.title || `Transaction ${i + 1}`, + assetSymbol, + yieldItem?.mechanics.type, + ), originalTitle: tx.title || '', type: tx.type, status: 'pending' as const, @@ -802,6 +817,7 @@ export const useYieldTransactionFlow = ({ assetSymbol, translate, showErrorToast, + yieldItem?.mechanics.type, ]) return useMemo(