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

Commit b4b3c9c

Browse files
authored
feat: expansion activity card (#1545)
1 parent 9ed8020 commit b4b3c9c

12 files changed

Lines changed: 143 additions & 20 deletions

File tree

src/ui/baby/widgets/StakingModal/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { PreviewModal, useFormContext } from "@babylonlabs-io/core-ui";
33
import babylon from "@/infrastructure/babylon";
44
import { ValidatorAvatar } from "@/ui/baby/components/ValidatorAvatar";
55
import { getNetworkConfigBBN } from "@/ui/common/config/network/bbn";
6+
import { DEFAULT_CONFIRMATION_DEPTH } from "@/ui/common/constants";
67
import { useNetworkInfo } from "@/ui/common/hooks/client/api/useNetworkInfo";
78
import { maxDecimals } from "@/ui/common/utils/maxDecimals";
89

@@ -17,7 +18,7 @@ export function StakingModal() {
1718
const { data: networkInfo } = useNetworkInfo();
1819
const confirmationDepth =
1920
networkInfo?.params.btcEpochCheckParams?.latestParam
20-
?.btcConfirmationDepth || 30;
21+
?.btcConfirmationDepth || DEFAULT_CONFIRMATION_DEPTH;
2122
const processingHours = Math.ceil(confirmationDepth / 6);
2223
const processingHourLabel = processingHours === 1 ? "hour" : "hours";
2324
const warnings = [
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { Text } from "@babylonlabs-io/core-ui";
2+
3+
import { DEFAULT_CONFIRMATION_DEPTH } from "@/ui/common/constants";
4+
import { useNetworkInfo } from "@/ui/common/hooks/client/api/useNetworkInfo";
5+
6+
interface ExpansionPendingBannerProps {
7+
className?: string;
8+
}
9+
10+
export function ExpansionPendingBanner({
11+
className = "",
12+
}: ExpansionPendingBannerProps) {
13+
const { data: networkInfo } = useNetworkInfo();
14+
const confirmationDepth =
15+
networkInfo?.params.btcEpochCheckParams?.latestParam
16+
?.btcConfirmationDepth || DEFAULT_CONFIRMATION_DEPTH;
17+
18+
return (
19+
<div
20+
className={`mb-4 p-4 bg-warning-surface border border-warning-strokeLight rounded ${className}`}
21+
>
22+
<Text variant="body1" className="text-accent-primary font-medium mb-2">
23+
Stake Expansion Pending
24+
</Text>
25+
<Text variant="body2" className="text-accent-secondary">
26+
Your stake expansion transaction has been forwarded to Bitcoin. It will
27+
be activated once it receives {confirmationDepth} Bitcoin block
28+
confirmations. Your original stake is still Active and you can find it
29+
in the "Expansion History" tab.
30+
</Text>
31+
</div>
32+
);
33+
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ import { ExpansionButton } from "./ExpansionButton";
2222

2323
interface StakeExpansionSectionProps {
2424
delegation: DelegationWithFP;
25+
isPendingExpansion?: boolean;
2526
}
2627

2728
export function StakeExpansionSection({
2829
delegation,
30+
isPendingExpansion = false,
2931
}: StakeExpansionSectionProps) {
3032
const {
3133
goToStep,
@@ -158,13 +160,18 @@ export function StakeExpansionSection({
158160
text="Add BSNs and Finality Providers"
159161
counter={`${currentBsnCount}/${maxFinalityProviders}`}
160162
onClick={handleAddBsnFp}
161-
disabled={!canExpandDelegation || processing || isUTXOsLoading}
163+
disabled={
164+
!canExpandDelegation ||
165+
processing ||
166+
isUTXOsLoading ||
167+
isPendingExpansion
168+
}
162169
/>
163170
<ExpansionButton
164171
Icon={iconRenew}
165172
text="Renew Staking Term"
166173
onClick={handleRenewStakingTerm}
167-
disabled={processing || isUTXOsLoading}
174+
disabled={processing || isUTXOsLoading || isPendingExpansion}
168175
/>
169176
<ExpansionButton
170177
Icon={iconHistory}

src/ui/common/components/ActivityCard/ActivityCard.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ExpansionPendingBanner } from "@/ui/common/components/Activity/components/ExpansionPendingBanner";
12
import { StakeExpansionSection } from "@/ui/common/components/Activity/components/StakeExpansionSection";
23
import { DelegationWithFP } from "@/ui/common/types/delegationsV2";
34
import FeatureFlagService from "@/ui/common/utils/FeatureFlagService";
@@ -44,6 +45,8 @@ export interface ActivityCardData {
4445
primaryAction?: ActivityCardActionButton;
4546
secondaryActions?: ActivityCardActionButton[];
4647
expansionSection?: DelegationWithFP;
48+
isPendingExpansion?: boolean;
49+
showExpansionPendingBanner?: boolean;
4750
hideExpansionCompletely?: boolean;
4851
}
4952

@@ -66,18 +69,19 @@ export function ActivityCard({ data, className }: ActivityCardProps) {
6669
iconAlt={data.iconAlt}
6770
primaryAction={data.primaryAction}
6871
/>
69-
7072
<ActivityCardDetailsSection
7173
details={data.details}
7274
optionalDetails={data.optionalDetails}
7375
listItems={data.listItems}
7476
groupedDetails={data.groupedDetails}
7577
/>
76-
78+
{data.showExpansionPendingBanner && <ExpansionPendingBanner />}
7779
{shouldShowExpansion && data.expansionSection && (
78-
<StakeExpansionSection delegation={data.expansionSection} />
80+
<StakeExpansionSection
81+
delegation={data.expansionSection}
82+
isPendingExpansion={data.isPendingExpansion}
83+
/>
7984
)}
80-
8185
{data.secondaryActions && data.secondaryActions.length > 0 && (
8286
<ActivityCardActionSection actions={data.secondaryActions} />
8387
)}

src/ui/common/components/ActivityCard/utils/actionButtonUtils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const getActionButton = (
1212
delegation: DelegationWithFP,
1313
onAction: (action: ActionType, delegation: DelegationWithFP) => void,
1414
isStakingManagerReady: boolean,
15+
isBroadcastedExpansion?: boolean,
1516
): ActivityCardActionButton | undefined => {
1617
const { state, fp } = delegation;
1718

@@ -125,6 +126,16 @@ export const getActionButton = (
125126
const actionConfig = actionMap[fp?.state]?.[state];
126127
if (!actionConfig) return undefined;
127128

129+
// Don't show "Stake" button for broadcasted VERIFIED expansions
130+
// since they're already broadcasted to Bitcoin
131+
if (
132+
isBroadcastedExpansion &&
133+
actionConfig.action === ACTIONS.STAKE &&
134+
state === DelegationV2StakingState.VERIFIED
135+
) {
136+
return undefined;
137+
}
138+
128139
const isUnbondDisabled =
129140
state === DelegationV2StakingState.ACTIVE && !isStakingManagerReady;
130141

src/ui/common/components/ActivityCard/utils/activityCardTransformers.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const { coinName, icon } = getNetworkConfigBTC();
1919
export interface ActivityCardTransformOptions {
2020
showExpansionSection?: boolean;
2121
hideExpansionCompletely?: boolean;
22+
isBroadcastedExpansion?: boolean;
2223
}
2324

2425
/**
@@ -32,7 +33,7 @@ export function transformDelegationToActivityCard(
3233
indexLabel?: string,
3334
): ActivityCardData {
3435
// Create a delegation with FP for the Status component if not already present
35-
const delegationWithFP =
36+
let delegationWithFP =
3637
"fp" in delegation
3738
? delegation
3839
: ({
@@ -47,6 +48,14 @@ export function transformDelegationToActivityCard(
4748
: undefined,
4849
} as DelegationWithFP);
4950

51+
// Transform the state for broadcasted expansions to show correct status
52+
if (options.isBroadcastedExpansion) {
53+
delegationWithFP = {
54+
...delegationWithFP,
55+
state: DelegationV2StakingState.INTERMEDIATE_PENDING_BTC_CONFIRMATION,
56+
};
57+
}
58+
5059
const details: ActivityCardDetailItem[] = [
5160
{
5261
label: "Status",
@@ -80,29 +89,42 @@ export function transformDelegationToActivityCard(
8089

8190
// Handle expansion section if options specify it
8291
let expansionSection: DelegationWithFP | undefined;
92+
let isPendingExpansion = false;
93+
8394
if (options.showExpansionSection) {
8495
// Check if expansion section should be shown
85-
// 1. Delegation is active
86-
// 2. Delegation can expand from the api
87-
const showExpansionSection =
96+
// 1. Delegation is active and can expand from the api
97+
// 2. OR delegation is a broadcasted VERIFIED expansion (waiting for confirmations)
98+
const isActiveExpandable =
8899
delegation.state === DelegationV2StakingState.ACTIVE &&
89100
delegation.canExpand;
90101

91-
expansionSection = showExpansionSection ? delegationWithFP : undefined;
102+
const showExpansionSection =
103+
isActiveExpandable || options.isBroadcastedExpansion;
104+
105+
if (showExpansionSection) {
106+
expansionSection = delegationWithFP;
107+
isPendingExpansion = !!options.isBroadcastedExpansion;
108+
}
92109
}
93110

94111
const baseAmount = `${maxDecimals(satoshiToBtc(delegation.stakingAmount), 8)} ${coinName}`;
95112
const formattedAmount = indexLabel
96113
? `${indexLabel} - ${baseAmount}`
97114
: baseAmount;
98115

116+
// Determine if we should show the expansion pending banner
117+
const showExpansionPendingBanner = !!options.isBroadcastedExpansion;
118+
99119
return {
100120
formattedAmount,
101121
icon: icon,
102122
iconAlt: "bitcoin",
103123
details,
104124
groupedDetails: groupedDetails.length > 0 ? groupedDetails : undefined,
105125
expansionSection,
126+
isPendingExpansion,
127+
showExpansionPendingBanner,
106128
hideExpansionCompletely: options.hideExpansionCompletely,
107129
};
108130
}

src/ui/common/components/Delegations/DelegationList/components/Status.tsx

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,29 @@ type StatusAdapter = (props: StatusParams) => {
3131

3232
const { coinName } = getNetworkConfigBTC();
3333

34+
/**
35+
* Helper function to get the status info for INTERMEDIATE_PENDING_BTC_CONFIRMATION state
36+
* Handles both regular staking and expansion scenarios
37+
*/
38+
const getIntermediatePendingBtcStatus = ({
39+
delegation,
40+
networkInfo,
41+
}: StatusParams) => {
42+
const confirmationDepth =
43+
networkInfo?.params?.btcEpochCheckParams.latestParam.btcConfirmationDepth ??
44+
0;
45+
const isExpansion = !!delegation.previousStakingTxHashHex;
46+
47+
return {
48+
label: isExpansion
49+
? `Expansion Pending ${coinName} Confirmation`
50+
: `Pending ${coinName} Confirmation`,
51+
tooltip: isExpansion
52+
? `Stake expansion is pending ${confirmationDepth} ${coinName} confirmations`
53+
: `Stake is pending ${confirmationDepth} ${coinName} confirmations`,
54+
};
55+
};
56+
3457
const STATUSES: Record<string, StatusAdapter> = {
3558
[State.PENDING]: () => ({
3659
label: "Pending",
@@ -113,10 +136,8 @@ const STATUSES: Record<string, StatusAdapter> = {
113136
label: "Pending",
114137
tooltip: "Stake is pending verification",
115138
}),
116-
[State.INTERMEDIATE_PENDING_BTC_CONFIRMATION]: ({ networkInfo }) => ({
117-
label: `Pending ${coinName} Confirmation`,
118-
tooltip: `Stake is pending ${networkInfo?.params?.btcEpochCheckParams.latestParam.btcConfirmationDepth ?? 0} ${coinName} confirmations`,
119-
}),
139+
[State.INTERMEDIATE_PENDING_BTC_CONFIRMATION]:
140+
getIntermediatePendingBtcStatus,
120141
[State.INTERMEDIATE_UNBONDING_SUBMITTED]: () => ({
121142
label: "Unbonding",
122143
tooltip: "Stake unbonding is in progress.",

src/ui/common/components/Modals/PreviewModal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Fragment } from "react";
1212

1313
import { getNetworkConfigBBN } from "@/ui/common/config/network/bbn";
1414
import { getNetworkConfigBTC } from "@/ui/common/config/network/btc";
15+
import { DEFAULT_CONFIRMATION_DEPTH } from "@/ui/common/constants";
1516
import { useNetworkInfo } from "@/ui/common/hooks/client/api/useNetworkInfo";
1617
import { usePrice } from "@/ui/common/hooks/client/api/usePrices";
1718
import { useIsMobileView } from "@/ui/common/hooks/useBreakpoint";
@@ -57,7 +58,7 @@ export const PreviewModal = ({
5758
const { data: networkInfo } = useNetworkInfo();
5859
const confirmationDepth =
5960
networkInfo?.params.btcEpochCheckParams?.latestParam
60-
?.btcConfirmationDepth || 30;
61+
?.btcConfirmationDepth || DEFAULT_CONFIRMATION_DEPTH;
6162
const unbondingTime =
6263
blocksToDisplayTime(
6364
networkInfo?.params.bbnStakingParams?.latestParam?.unbondingTime,

src/ui/common/components/Multistaking/MultistakingModal/MultistakingModal.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { VerificationModal } from "@/ui/common/components/Modals/VerificationMod
1414
import { FinalityProviderLogo } from "@/ui/common/components/Staking/FinalityProviders/FinalityProviderLogo";
1515
import { getNetworkConfigBBN } from "@/ui/common/config/network/bbn";
1616
import { getNetworkConfigBTC } from "@/ui/common/config/network/btc";
17+
import { DEFAULT_CONFIRMATION_DEPTH } from "@/ui/common/constants";
1718
import { useNetworkInfo } from "@/ui/common/hooks/client/api/useNetworkInfo";
1819
import { usePrice } from "@/ui/common/hooks/client/api/usePrices";
1920
import { useStakingService } from "@/ui/common/hooks/services/useStakingService";
@@ -68,7 +69,7 @@ export function MultistakingModal() {
6869
const btcInUsd = usePrice(coinSymbol);
6970
const confirmationDepth =
7071
networkInfo?.params.btcEpochCheckParams?.latestParam
71-
?.btcConfirmationDepth || 30;
72+
?.btcConfirmationDepth || DEFAULT_CONFIRMATION_DEPTH;
7273

7374
const currentFinalityProviders = useWatch<
7475
{ finalityProviders: Record<string, string> | string[] | undefined },

src/ui/common/components/StakingExpansion/StakingExpansionModalSystem.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ import { VerificationModal } from "@/ui/common/components/Modals/VerificationMod
99
import { FinalityProviderLogo } from "@/ui/common/components/Staking/FinalityProviders/FinalityProviderLogo";
1010
import { getNetworkConfigBBN } from "@/ui/common/config/network/bbn";
1111
import { getNetworkConfigBTC } from "@/ui/common/config/network/btc";
12-
import { BaseStakingStep, EOIStep } from "@/ui/common/constants";
12+
import {
13+
BaseStakingStep,
14+
DEFAULT_CONFIRMATION_DEPTH,
15+
EOIStep,
16+
} from "@/ui/common/constants";
1317
import { useNetworkInfo } from "@/ui/common/hooks/client/api/useNetworkInfo";
1418
import { usePrice } from "@/ui/common/hooks/client/api/usePrices";
1519
import { useStakingExpansionService } from "@/ui/common/hooks/services/useStakingExpansionService";
@@ -263,7 +267,7 @@ function StakingExpansionModalSystemInner() {
263267
}}
264268
warnings={[
265269
`1. No third party possesses your staked ${coinSymbol}. You are the only one who can unbond and withdraw your stake.`,
266-
`2. Your stake will first be sent to Babylon Genesis for verification (~20 seconds), then you will be prompted to submit it to the Bitcoin ledger. It will be marked as 'Pending' until it receives ${networkInfoData?.params?.btcEpochCheckParams?.latestParam?.btcConfirmationDepth ?? 30} Bitcoin confirmations.`,
270+
`2. Your stake will first be sent to Babylon Genesis for verification (~20 seconds), then you will be prompted to submit it to the Bitcoin ledger. It will be marked as 'Pending' until it receives ${networkInfoData?.params?.btcEpochCheckParams?.latestParam?.btcConfirmationDepth ?? DEFAULT_CONFIRMATION_DEPTH} Bitcoin confirmations.`,
267271
"3. Please note: submitting this transaction will reset your stake's timelock.",
268272
]}
269273
/>

0 commit comments

Comments
 (0)