Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit beb3d77

Browse files
committed
feat: expansion activity
1 parent 9728e9d commit beb3d77

7 files changed

Lines changed: 247 additions & 112 deletions

File tree

src/ui/common/components/Activity/components/ActivityList.tsx

Lines changed: 8 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,110 +1,32 @@
1-
import { useMemo } from "react";
2-
31
import { ExpansionHistoryModal } from "@/ui/common/components/ExpansionHistory/ExpansionHistoryModal";
42
import { getNetworkConfig } from "@/ui/common/config/network";
5-
import { useDelegationService } from "@/ui/common/hooks/services/useDelegationService";
6-
import { useStakingManagerService } from "@/ui/common/hooks/services/useStakingManagerService";
7-
import { useFinalityProviderState } from "@/ui/common/state/FinalityProviderState";
3+
import { useActivityDelegations } from "@/ui/common/hooks/services/useActivityDelegations";
84
import { useStakingExpansionState } from "@/ui/common/state/StakingExpansionState";
9-
import {
10-
DelegationV2,
11-
DelegationV2StakingState,
12-
DelegationWithFP,
13-
} from "@/ui/common/types/delegationsV2";
145

156
import { ActivityCard } from "../../ActivityCard/ActivityCard";
16-
import { getActionButton } from "../../ActivityCard/utils/actionButtonUtils";
17-
import {
18-
ActivityCardTransformOptions,
19-
transformDelegationToActivityCard,
20-
} from "../../ActivityCard/utils/activityCardTransformers";
217
import { DelegationModal } from "../../Delegations/DelegationList/components/DelegationModal";
228
import { StakingExpansionModalSystem } from "../../StakingExpansion/StakingExpansionModalSystem";
239

2410
const networkConfig = getNetworkConfig();
2511

2612
export function ActivityList() {
13+
// All business logic is now centralized in this hook
2714
const {
15+
activityData,
16+
isLoading,
2817
processing,
2918
confirmationModal,
30-
delegations,
31-
isLoading,
32-
validations,
3319
executeDelegationAction,
34-
openConfirmationModal,
3520
closeConfirmationModal,
36-
} = useDelegationService();
37-
38-
const { isLoading: isStakingManagerLoading } = useStakingManagerService();
39-
const isStakingManagerReady = !isStakingManagerLoading;
40-
41-
const { finalityProviderMap } = useFinalityProviderState();
21+
delegations,
22+
} = useActivityDelegations();
4223

4324
const {
4425
expansionHistoryModalOpen,
4526
expansionHistoryTargetDelegation,
4627
closeExpansionHistoryModal,
4728
} = useStakingExpansionState();
4829

49-
const activityList = useMemo(() => {
50-
return delegations
51-
.filter((delegation) => {
52-
const { valid } = validations[delegation.stakingTxHashHex];
53-
return valid;
54-
})
55-
.filter(
56-
// Filter out expanded delegations as they are now part of the
57-
// expanded delegation. User can find it from delegation history.
58-
(delegation) => delegation.state !== DelegationV2StakingState.EXPANDED,
59-
)
60-
.map((delegation) => {
61-
const options: ActivityCardTransformOptions = {
62-
showExpansionSection: true,
63-
};
64-
const cardData = transformDelegationToActivityCard(
65-
delegation,
66-
finalityProviderMap,
67-
options,
68-
);
69-
70-
// Create delegation with FP for action button
71-
// delegations from useDelegationService are DelegationV2 objects
72-
const delegation_v2 = delegation as DelegationV2;
73-
// Use the first FP [0] for backward compatibility with action button logic
74-
// which expects a single FP to determine button state. The full BSN/FP pairs
75-
// are properly displayed in the card's grouped details section
76-
const fp =
77-
Array.isArray(delegation_v2.finalityProviderBtcPksHex) &&
78-
delegation_v2.finalityProviderBtcPksHex.length > 0
79-
? finalityProviderMap.get(
80-
delegation_v2.finalityProviderBtcPksHex[0],
81-
)
82-
: undefined;
83-
const delegationWithFP: DelegationWithFP = {
84-
...delegation_v2,
85-
fp,
86-
} as DelegationWithFP;
87-
88-
// Add action button if applicable
89-
const primaryAction = getActionButton(
90-
delegationWithFP,
91-
openConfirmationModal,
92-
isStakingManagerReady,
93-
);
94-
95-
return {
96-
...cardData,
97-
primaryAction,
98-
};
99-
});
100-
}, [
101-
delegations,
102-
validations,
103-
openConfirmationModal,
104-
isStakingManagerReady,
105-
finalityProviderMap,
106-
]);
107-
10830
if (isLoading) {
10931
return (
11032
<div className="flex justify-center items-center py-8">
@@ -113,7 +35,7 @@ export function ActivityList() {
11335
);
11436
}
11537

116-
if (activityList.length === 0) {
38+
if (activityData.length === 0) {
11739
return (
11840
<div className="flex flex-col pb-16 pt-6 text-accent-primary gap-4 text-center items-center justify-center">
11941
<h4 className="text-xl font-semibold">
@@ -127,7 +49,7 @@ export function ActivityList() {
12749
return (
12850
<>
12951
<div className="space-y-4">
130-
{activityList.map((data, index) => (
52+
{activityData.map((data, index) => (
13153
<ActivityCard
13254
key={delegations[index]?.stakingTxHashHex || index}
13355
data={data}

src/ui/common/components/Stats/Stats.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export const Stats = memo(() => {
5656
tooltip={
5757
FeatureFlagService.IsPhase3Enabled
5858
? "This is the maximum achievable APR based on delegating to the highest-yielding BSNs, including Babylon Genesis. Actual APR may vary depending on your selection."
59-
: 'Annual Percentage Reward (APR) is a dynamic estimate of the annualized staking reward rate based on current network conditions, and it refers to staking rewards rather than traditional lending interest. Rewards are distributed in BABY tokens but shown as a Bitcoin-equivalent rate relative to the Bitcoin initially staked. APR is calculated using U.S. dollar values for Bitcoin and BABY from independent, reputable sources. The APR shown is an approximate figure that can fluctuate, and the displayed value may not always be completely accurate. Actual rewards are not guaranteed and may vary over time. Staking carries exposure to slashing and other risks.'
59+
: "Annual Percentage Reward (APR) is a dynamic estimate of the annualized staking reward rate based on current network conditions, and it refers to staking rewards rather than traditional lending interest. Rewards are distributed in BABY tokens but shown as a Bitcoin-equivalent rate relative to the Bitcoin initially staked. APR is calculated using U.S. dollar values for Bitcoin and BABY from independent, reputable sources. The APR shown is an approximate figure that can fluctuate, and the displayed value may not always be completely accurate. Actual rewards are not guaranteed and may vary over time. Staking carries exposure to slashing and other risks."
6060
}
6161
/>
6262

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { useMemo } from "react";
2+
3+
import { getActionButton } from "@/ui/common/components/ActivityCard/utils/actionButtonUtils";
4+
import {
5+
ActivityCardTransformOptions,
6+
transformDelegationToActivityCard,
7+
} from "@/ui/common/components/ActivityCard/utils/activityCardTransformers";
8+
import { ActionType } from "@/ui/common/hooks/services/useDelegationService";
9+
import {
10+
DelegationV2,
11+
DelegationWithFP,
12+
} from "@/ui/common/types/delegationsV2";
13+
import { FinalityProvider } from "@/ui/common/types/finalityProviders";
14+
15+
export interface ActivityCardData
16+
extends ReturnType<typeof transformDelegationToActivityCard> {
17+
primaryAction?: ReturnType<typeof getActionButton>;
18+
}
19+
20+
/**
21+
* Hook for transforming delegations into activity card data.
22+
* Handles transformation to card format and addition of action buttons.
23+
*
24+
* @param delegations - The validated delegations to transform
25+
* @param finalityProviderMap - Map of finality providers for FP lookup
26+
* @param openConfirmationModal - Function to open confirmation modal
27+
* @param isStakingManagerReady - Whether staking manager is ready for actions
28+
* @returns Array of transformed activity card data
29+
*/
30+
export function useActivityCardTransformation(
31+
delegations: DelegationV2[],
32+
finalityProviderMap: Map<string, FinalityProvider>,
33+
openConfirmationModal: (
34+
action: ActionType,
35+
delegation: DelegationWithFP,
36+
) => void,
37+
isStakingManagerReady: boolean,
38+
): ActivityCardData[] {
39+
return useMemo(() => {
40+
return delegations.map((delegation) => {
41+
// Transform delegation to activity card format
42+
const options: ActivityCardTransformOptions = {
43+
showExpansionSection: true,
44+
};
45+
const cardData = transformDelegationToActivityCard(
46+
delegation,
47+
finalityProviderMap,
48+
options,
49+
);
50+
51+
// Create delegation with FP for action button logic
52+
// Use the first FP [0] for backward compatibility with action button logic
53+
// which expects a single FP to determine button state. The full BSN/FP pairs
54+
// are properly displayed in the card's grouped details section
55+
const delegation_v2 = delegation as DelegationV2;
56+
const fp =
57+
Array.isArray(delegation_v2.finalityProviderBtcPksHex) &&
58+
delegation_v2.finalityProviderBtcPksHex.length > 0
59+
? finalityProviderMap.get(delegation_v2.finalityProviderBtcPksHex[0])
60+
: undefined;
61+
62+
const delegationWithFP: DelegationWithFP = {
63+
...delegation_v2,
64+
fp,
65+
} as DelegationWithFP;
66+
67+
// Add action button if applicable
68+
const primaryAction = getActionButton(
69+
delegationWithFP,
70+
openConfirmationModal,
71+
isStakingManagerReady,
72+
);
73+
74+
return {
75+
...cardData,
76+
primaryAction,
77+
};
78+
});
79+
}, [
80+
delegations,
81+
finalityProviderMap,
82+
openConfirmationModal,
83+
isStakingManagerReady,
84+
]);
85+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { useBTCWallet } from "@/ui/common/context/wallet/BTCWalletProvider";
2+
import { useDelegationService } from "@/ui/common/hooks/services/useDelegationService";
3+
import { useExpansionVisibilityService } from "@/ui/common/hooks/services/useExpansionVisibilityService";
4+
import { useStakingManagerService } from "@/ui/common/hooks/services/useStakingManagerService";
5+
import { useFinalityProviderState } from "@/ui/common/state/FinalityProviderState";
6+
7+
import {
8+
ActivityCardData,
9+
useActivityCardTransformation,
10+
} from "./useActivityCardTransformation";
11+
import { useActivityValidation } from "./useActivityValidation";
12+
13+
/**
14+
* Main hook orchestrating all activity-related delegation logic.
15+
* Combines validation, visibility filtering, and card transformation.
16+
*/
17+
export function useActivityDelegations() {
18+
// Core delegation and wallet data
19+
const {
20+
processing,
21+
confirmationModal,
22+
delegations,
23+
isLoading: isDelegationLoading,
24+
validations,
25+
executeDelegationAction,
26+
openConfirmationModal,
27+
closeConfirmationModal,
28+
} = useDelegationService();
29+
30+
const { isLoading: isStakingManagerLoading } = useStakingManagerService();
31+
const isStakingManagerReady = !isStakingManagerLoading;
32+
33+
const { finalityProviderMap } = useFinalityProviderState();
34+
const { publicKeyNoCoord } = useBTCWallet();
35+
36+
// Expansion visibility service for filtering logic
37+
const { getActivityTabDelegations } =
38+
useExpansionVisibilityService(publicKeyNoCoord);
39+
40+
// Step 1: Apply validation logic (including special VERIFIED expansion handling)
41+
const validatedDelegations = useActivityValidation(
42+
delegations,
43+
validations,
44+
publicKeyNoCoord,
45+
);
46+
47+
// Step 2: Apply expansion visibility filtering
48+
const visibleDelegations = getActivityTabDelegations(validatedDelegations);
49+
50+
// Step 3: Transform to activity card data
51+
const activityData: ActivityCardData[] = useActivityCardTransformation(
52+
visibleDelegations,
53+
finalityProviderMap,
54+
openConfirmationModal,
55+
isStakingManagerReady,
56+
);
57+
58+
return {
59+
// Transformed data ready for rendering
60+
activityData,
61+
62+
// Loading states
63+
isLoading: isDelegationLoading,
64+
isStakingManagerReady,
65+
66+
// Modal states and actions
67+
processing,
68+
confirmationModal,
69+
executeDelegationAction,
70+
closeConfirmationModal,
71+
72+
// Raw delegations for modals that need them
73+
delegations,
74+
};
75+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { useMemo } from "react";
2+
3+
import { DelegationV2 } from "@/ui/common/types/delegationsV2";
4+
import { isValidBroadcastedExpansion } from "@/ui/common/utils/stakingExpansionValidation";
5+
6+
/**
7+
* Hook for validating delegations in the Activity tab.
8+
* Handles both regular UTXO validation and special cases for VERIFIED broadcasted expansions.
9+
*
10+
* @param delegations - The raw delegations from the delegation service
11+
* @param validations - The validation results from useUtxoValidation
12+
* @param publicKeyNoCoord - The user's public key for localStorage access
13+
* @returns Filtered array of delegations that should be considered for display
14+
*/
15+
export function useActivityValidation(
16+
delegations: DelegationV2[],
17+
validations: Record<string, { valid: boolean }>,
18+
publicKeyNoCoord: string | undefined,
19+
): DelegationV2[] {
20+
return useMemo(() => {
21+
return delegations.filter((delegation) => {
22+
const validation = validations[delegation.stakingTxHashHex];
23+
const { valid } = validation || { valid: false };
24+
25+
// Include if it passes regular UTXO validation
26+
if (valid) return true;
27+
28+
// Special case: Include VERIFIED broadcasted expansions even without valid UTXOs
29+
// This is because expansions are broadcasted but not yet confirmed on Bitcoin,
30+
// so they don't have valid UTXOs yet but should still be shown in Activity tab
31+
if (isValidBroadcastedExpansion(delegation, publicKeyNoCoord)) {
32+
return true;
33+
}
34+
35+
return false;
36+
});
37+
}, [delegations, validations, publicKeyNoCoord]);
38+
}

0 commit comments

Comments
 (0)