Skip to content

Commit

Permalink
fix: [lw-11875] differentiate between locked and spendable rewards in…
Browse files Browse the repository at this point in the history
… activity details
  • Loading branch information
vetalcore committed Nov 22, 2024
1 parent a701c92 commit 86be91c
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@ import { inspectTxValues } from '@src/utils/tx-inspection';
import { firstValueFrom } from 'rxjs';
import { getAssetsInformation } from '@src/utils/get-assets-information';
import { MAX_POOLS_COUNT } from '@lace/staking';
import {
ActivityStatus,
ConwayEraCertificatesTypes,
DelegationActivityType,
TransactionActivityType
} from '@lace/core';
import { ConwayEraCertificatesTypes, DelegationActivityType, TransactionActivityType } from '@lace/core';
import type { ActivityType } from '@lace/core';
import { formatDate, formatTime } from '@src/utils/format-date';
import { createHistoricalOwnInputResolver, HistoricalOwnInputResolverArgs } from '@src/utils/own-input-resolver';
Expand Down Expand Up @@ -321,7 +316,7 @@ export const activityDetailSlice: SliceCreator<
getActivityDetail: buildGetActivityDetail({ set, get }),
setTransactionActivityDetail: ({ activity, direction, status, type }) =>
set({ activityDetail: { activity, direction, status, type } }),
setRewardsActivityDetail: ({ activity }) =>
set({ activityDetail: { activity, status: ActivityStatus.SPENDABLE, type: TransactionActivityType.rewards } }),
setRewardsActivityDetail: ({ activity, status }) =>
set({ activityDetail: { activity, status, type: TransactionActivityType.rewards } }),
resetActivityState: () => set({ activityDetail: undefined, fetchingActivityInfo: false })
});
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { createHistoricalOwnInputResolver } from '@src/utils/own-input-resolver'
import { isKeyHashAddress } from '@cardano-sdk/wallet';
import { ObservableWalletState } from '@hooks/useWalletState';
import { IBlockchainProvider } from './blockchain-provider-slice';
import { getPoolIdToRewardAccountMap } from '@src/views/browser-view/features/staking/hooks';

export interface FetchWalletActivitiesProps {
fiatCurrency: CurrencyInfo;
Expand Down Expand Up @@ -79,6 +80,8 @@ type DelegationActivityItemProps = Omit<ExtendedActivityProps, 'type'> & {
type: extendedDelegationActivityType;
};

const PV10_NETWORK_VERSION = 10;

const isDelegationActivity = (activity: ExtendedActivityProps): activity is DelegationActivityItemProps =>
activity.type in DelegationActivityType ||
activity.type === ConwayEraCertificatesTypes.Registration ||
Expand Down Expand Up @@ -123,7 +126,7 @@ const mapWalletActivities = memoize(
eraSummaries,
protocolParameters,
assetInfo,
delegation: { rewardsHistory }
delegation: { rewardsHistory, rewardAccounts }
}: ObservableWalletState,
{ fiatCurrency, cardanoFiatPrice, sendAnalytics }: FetchWalletActivitiesProps,
{
Expand All @@ -139,25 +142,32 @@ const mapWalletActivities = memoize(
Pick<IBlockchainProvider, 'assetProvider'> &
Pick<WalletInfoSlice, 'isSharedWallet'>
) => {
const poolIdToRewardAccountMap = getPoolIdToRewardAccountMap(rewardAccounts);

const epochRewardsMapper = (earnedEpoch: Wallet.Cardano.EpochNo, rewards: Reward[]): ExtendedActivityProps => {
const REWARD_SPENDABLE_DELAY_EPOCHS = 2;
const spendableEpoch = (earnedEpoch + REWARD_SPENDABLE_DELAY_EPOCHS) as Wallet.Cardano.EpochNo;
const slotTimeCalc = Wallet.createSlotTimeCalc(eraSummaries);
const rewardSpendableDate = slotTimeCalc(epochSlotsCalc(spendableEpoch, eraSummaries).firstSlot);
const isPv10Network = protocolParameters.protocolVersion.major >= PV10_NETWORK_VERSION;
const isRewardSpendable =
isPv10Network && !rewards.some(({ poolId }) => poolId && !poolIdToRewardAccountMap.get(poolId)?.dRepDelegatee);

const transformedEpochRewards = rewardHistoryTransformer({
rewards,
fiatCurrency,
fiatPrice: cardanoFiatPrice,
cardanoCoin,
date: rewardSpendableDate
date: rewardSpendableDate,
isRewardSpendable
});

return {
...transformedEpochRewards,
onClick: () => {
if (sendAnalytics) sendAnalytics();
setRewardsActivityDetail({
status: transformedEpochRewards.status,
activity: {
rewards,
spendableEpoch,
Expand Down
9 changes: 7 additions & 2 deletions apps/browser-extension-wallet/src/stores/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export interface ActivityDetailSlice {
} & (
| {
type: TransactionActivityType.rewards;
status: ActivityStatus.SPENDABLE;
status: ActivityStatus.SPENDABLE | ActivityStatus.LOCKED;
direction?: never;
activity: { spendableEpoch: Cardano.EpochNo; spendableDate: Date; rewards: Reward[] };
}
Expand All @@ -165,7 +165,12 @@ export interface ActivityDetailSlice {
type: Exclude<ActivityType, TransactionActivityType.rewards>;
}) => void;
setRewardsActivityDetail: (params: {
activity: { spendableEpoch: Cardano.EpochNo; spendableDate: Date; rewards: Reward[] };
status: ActivityStatus.SPENDABLE | ActivityStatus.LOCKED;
activity: {
spendableEpoch: Cardano.EpochNo;
spendableDate: Date;
rewards: Reward[];
};
}) => void;
getActivityDetail: (params: { coinPrices: PriceResult; fiatCurrency: CurrencyInfo }) => Promise<ActivityDetail>;
resetActivityState: () => void;
Expand Down
2 changes: 1 addition & 1 deletion apps/browser-extension-wallet/src/types/activity-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export type TransactionActivityDetail = {

export type RewardsActivityDetail = {
type: TransactionActivityType.rewards;
status: ActivityStatus.SPENDABLE;
status: ActivityStatus.SPENDABLE | ActivityStatus.LOCKED;
activity: RewardsActivity;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@ interface RewardHistoryTransformerInput {
fiatPrice: number;
date: Date;
cardanoCoin: Wallet.CoinId;
isRewardSpendable: boolean;
}

export const rewardHistoryTransformer = ({
rewards,
fiatCurrency,
fiatPrice,
date,
cardanoCoin
}: RewardHistoryTransformerInput): TransformedRewardsActivity => {
cardanoCoin,
isRewardSpendable
}: RewardHistoryTransformerInput): Omit<TransformedRewardsActivity, 'status'> & {
status: ActivityStatus.SPENDABLE | ActivityStatus.LOCKED;
} => {
const formattedTimestamp = formatTime({
date,
type: 'local'
Expand All @@ -42,7 +46,7 @@ export const rewardHistoryTransformer = ({
fiatCurrency,
fiatPrice
}),
status: ActivityStatus.SPENDABLE,
status: isRewardSpendable ? ActivityStatus.SPENDABLE : ActivityStatus.LOCKED,
assets: [],
date,
formattedTimestamp,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';
import { useDelegationStore } from '@src/features/delegation/stores';
import { useWalletStore } from '@stores';
import { withSignTxConfirmation } from '@lib/wallet-api-ui';
import { useSecrets } from '@lace/core';
import { useObservable } from '@lace/common';
import { Wallet } from '@lace/cardano';

interface UseRewardAccountsDataType {
accountsWithRegisteredStakeCredsWithoutVotingDelegation: Wallet.Cardano.RewardAccountInfo[];
accountsWithRegisteredStakeCreds: Wallet.Cardano.RewardAccountInfo[];
poolIdToRewardAccountMap: Map<string, Wallet.Cardano.RewardAccountInfo>;
lockedStakeRewards: BigInt;
}

export const useDelegationTransaction = (): { signAndSubmitTransaction: () => Promise<void> } => {
const { password, clearSecrets } = useSecrets();
Expand All @@ -17,3 +26,54 @@ export const useDelegationTransaction = (): { signAndSubmitTransaction: () => Pr

return { signAndSubmitTransaction };
};

export const getPoolIdToRewardAccountMap = (
rewardAccounts: Wallet.Cardano.RewardAccountInfo[]
): UseRewardAccountsDataType['poolIdToRewardAccountMap'] =>
new Map(
rewardAccounts
?.map((rewardAccount): [string, Wallet.Cardano.RewardAccountInfo] => {
const { delegatee } = rewardAccount;
const delagationInfo = delegatee?.nextNextEpoch || delegatee?.nextEpoch || delegatee?.currentEpoch;

return [delagationInfo?.id.toString(), rewardAccount];
})
.filter(([poolId]) => !!poolId)
);

export const useRewardAccountsData = (): UseRewardAccountsDataType => {
const { inMemoryWallet } = useWalletStore();
const rewardAccounts = useObservable(inMemoryWallet.delegation.rewardAccounts$);
const accountsWithRegisteredStakeCreds = rewardAccounts?.filter(
({ credentialStatus }) => Wallet.Cardano.StakeCredentialStatus.Registered === credentialStatus
);

const accountsWithRegisteredStakeCredsWithoutVotingDelegation = useMemo(
() => accountsWithRegisteredStakeCreds?.filter(({ dRepDelegatee }) => !dRepDelegatee),
[accountsWithRegisteredStakeCreds]
);

const lockedStakeRewards = useMemo(
() =>
BigInt(
accountsWithRegisteredStakeCredsWithoutVotingDelegation
? Wallet.BigIntMath.sum(
accountsWithRegisteredStakeCredsWithoutVotingDelegation.map(({ rewardBalance }) => rewardBalance)
)
: 0
),
[accountsWithRegisteredStakeCredsWithoutVotingDelegation]
);

const poolIdToRewardAccountMap = useMemo(
() => getPoolIdToRewardAccountMap(accountsWithRegisteredStakeCreds),
[accountsWithRegisteredStakeCreds]
);

return {
accountsWithRegisteredStakeCreds,
accountsWithRegisteredStakeCredsWithoutVotingDelegation,
lockedStakeRewards,
poolIdToRewardAccountMap
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const ActivityStatusIcon = ({ status, type }: ActivityStatusIconProps) => {
case ActivityStatus.SUCCESS:
return <ActivityTypeIcon type={type} />;
case ActivityStatus.SPENDABLE:
case ActivityStatus.LOCKED:
return <ActivityTypeIcon type={TransactionActivityType.rewards} />;
case ActivityStatus.PENDING:
return <Icon component={PendingIcon} style={iconStyle} data-testid="activity-status" />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type RewardsInfo = {
export interface RewardsDetailsProps {
name: string;
headerDescription?: string;
status: ActivityStatus.SPENDABLE;
status: ActivityStatus.SPENDABLE | ActivityStatus.LOCKED;
includedDate: string;
includedTime: string;
amountTransformer: (amount: string) => string;
Expand Down Expand Up @@ -121,9 +121,9 @@ export const RewardsDetails = ({
<div className={styles.title} data-testid="rewards-status-title">
{t('core.activityDetails.status')}
</div>
<div data-testid="rewards-status" className={styles.detail}>{`${status
.charAt(0)
.toUpperCase()}${status.slice(1)}`}</div>
<div data-testid="rewards-status" className={styles.detail}>
{t(`core.activityDetails.statuses.${status}`)}
</div>
</div>
<div className={styles.details}>
<div className={styles.title} data-testid="rewards-epoch-title">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const TransactionDetails = ({
const getCollateralStatus = (): CollateralStatus => {
switch (status) {
case ActivityStatus.SPENDABLE:
case ActivityStatus.LOCKED:
return CollateralStatus.NONE;
case ActivityStatus.PENDING:
case ActivityStatus.AWAITING_COSIGNATURES:
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/ui/components/Transaction/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum ActivityStatus {
PENDING = 'sending',
ERROR = 'error',
SPENDABLE = 'spendable',
LOCKED = 'locked',
AWAITING_COSIGNATURES = 'awaiting_cosignatures'
}

Expand Down
6 changes: 6 additions & 0 deletions packages/translation/src/lib/translations/core/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@
"core.activityDetails.StakeVoteDelegationCertificate": "Stake Key Registration & DRep Delegation",
"core.activityDetails.StakeVoteRegistrationDelegateCertificate": "Stake Key Registration, Delegation, & DRep Delegation",
"core.activityDetails.status": "Status",
"core.activityDetails.statuses.success": "Success",
"core.activityDetails.statuses.sending": "Sending",
"core.activityDetails.statuses.error": "Error",
"core.activityDetails.statuses.spendable": "Spendable",
"core.activityDetails.statuses.locked": "Locked",
"core.activityDetails.statuses.awaiting_cosignatures": "Awaiting Cosignatures",
"core.activityDetails.summary": "Summary",
"core.activityDetails.timestamp": "Timestamp",
"core.activityDetails.to": "To",
Expand Down

0 comments on commit 86be91c

Please sign in to comment.