Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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., `<TbCopy />`) 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)
Expand All @@ -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`
Expand Down
48 changes: 17 additions & 31 deletions src/assets/translations/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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}",
Expand All @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
23 changes: 16 additions & 7 deletions src/components/MultiHopTrade/components/Earn/EarnConfirm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -297,9 +308,7 @@ export const EarnConfirm = memo(() => {
{providerInfo && (
<HStack justify='space-between' mt={3}>
<Text color='text.subtle' fontSize='sm'>
{selectedValidator
? translate('yieldXYZ.validator')
: translate('yieldXYZ.provider')}
{translate(selectedValidator ? 'yieldXYZ.validator' : 'yieldXYZ.provider')}
</Text>
<HStack spacing={2}>
<Avatar size='xs' src={providerInfo.logoURI} name={providerInfo.name} />
Expand Down
11 changes: 10 additions & 1 deletion src/lib/yieldxyz/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -230,7 +239,7 @@ export type YieldEntryLimits = {
}

export type YieldMechanics = {
type: string
type: YieldType
requiresValidatorSelection: boolean
rewardSchedule: string
rewardClaiming: string
Expand Down
82 changes: 65 additions & 17 deletions src/lib/yieldxyz/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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',
})
})
Expand All @@ -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', () => {
Expand All @@ -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')
})
})
Loading