Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 4 additions & 4 deletions src/ui/common/hooks/services/useExpansionVisibilityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function useExpansionVisibilityService(
* Checks if a delegation is a broadcasted expansion.
* A broadcasted expansion must meet both criteria:
* 1. Has VERIFIED status in the API data (delegation.state)
* 2. Has INTERMEDIATE_PENDING_VERIFICATION status in localStorage (user broadcasted it)
* 2. Has INTERMEDIATE_PENDING_BTC_CONFIRMATION status in localStorage (user broadcasted it)
*/
const isBroadcastedExpansion = useCallback(
(delegation: DelegationV2): boolean => {
Expand All @@ -40,7 +40,7 @@ export function useExpansionVisibilityService(
return false;
}

// Then check if this delegation exists in localStorage with INTERMEDIATE_PENDING_VERIFICATION status
// Then check if this delegation exists in localStorage with INTERMEDIATE_PENDING_BTC_CONFIRMATION status
const storedDelegation = (expansionStorageDelegations ?? []).find(
(stored) =>
stored.stakingTxHashHex.toLowerCase() ===
Expand All @@ -49,7 +49,7 @@ export function useExpansionVisibilityService(

return (
storedDelegation?.state ===
DelegationV2StakingState.INTERMEDIATE_PENDING_VERIFICATION
DelegationV2StakingState.INTERMEDIATE_PENDING_BTC_CONFIRMATION
);
},
[expansionStorageDelegations],
Expand Down Expand Up @@ -77,7 +77,7 @@ export function useExpansionVisibilityService(
* Returns delegations that should be visible in the Activity tab.
* Applies the following rules:
* 1. Exclude VERIFIED expansions that are not broadcasted (show in modal only)
* 2. Include VERIFIED expansions that are broadcasted (INTERMEDIATE_PENDING_VERIFICATION)
* 2. Include VERIFIED expansions that are broadcasted (INTERMEDIATE_PENDING_BTC_CONFIRMATION)
* 3. Exclude original transactions that have broadcasted expansions
* 4. Include all other regular transactions
*/
Expand Down
8 changes: 8 additions & 0 deletions src/ui/common/hooks/services/useStakingExpansionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
DelegationV2,
} from "@/ui/common/types/delegationsV2";
import { retry } from "@/ui/common/utils";
import { markExpansionAsBroadcasted } from "@/ui/common/utils/local_storage/expansionStorage";
import { getTxHex } from "@/ui/common/utils/mempool_api";
import { validateExpansionFormData } from "@/ui/common/utils/stakingExpansionValidation";

Expand Down Expand Up @@ -388,6 +389,12 @@ export function useStakingExpansionService() {
DelegationState.INTERMEDIATE_PENDING_BTC_CONFIRMATION,
);

// Mark expansion as broadcasted in localStorage for visibility tracking
markExpansionAsBroadcasted(
delegation.stakingTxHashHex,
publicKeyNoCoord,
);

// Navigate to success
goToStep(StakingExpansionStep.FEEDBACK_SUCCESS);
setProcessing(false);
Expand All @@ -409,6 +416,7 @@ export function useStakingExpansionService() {
reset,
isUTXOsLoading,
availableUTXOs,
publicKeyNoCoord,
],
);

Expand Down
174 changes: 174 additions & 0 deletions src/ui/common/utils/local_storage/expansionStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import {
DelegationV2,
DelegationV2StakingState,
} from "@/ui/common/types/delegationsV2";

import { getExpansionsLocalStorageKey } from "./getExpansionsLocalStorageKey";

/**
* Helper function to remove an item from a localStorage record.
* This centralizes the localStorage record manipulation pattern used throughout the file.
*
* @param storageKey - The localStorage key
* @param itemId - The ID of the item to remove from the record
*/
function removeFromLocalStorageRecord(
storageKey: string,
itemId: string,
): void {
try {
const data = localStorage.getItem(storageKey);
if (data) {
const record = JSON.parse(data);
if (record[itemId]) {
delete record[itemId];
localStorage.setItem(storageKey, JSON.stringify(record));
}
}
} catch (error) {
console.error(
`Failed to remove item from localStorage key ${storageKey}:`,
error,
);
}
}

/**
* Mark an expansion as broadcasted by updating its status to INTERMEDIATE_PENDING_BTC_CONFIRMATION.
* This is called after a verified expansion is successfully signed and broadcasted to Bitcoin.
*
* @param expansionTxHashHex - The transaction hash of the expansion to mark as broadcasted
* @param publicKeyNoCoord - The public key of the wallet
*/
export function markExpansionAsBroadcasted(
expansionTxHashHex: string,
publicKeyNoCoord: string | undefined,
): void {
if (!publicKeyNoCoord) {
console.warn(
"Cannot mark expansion as broadcasted: no public key provided",
);
return;
}

const storageKey = getExpansionsLocalStorageKey(publicKeyNoCoord);
const statusesKey = `${storageKey}_statuses`;

try {
// Get existing statuses from localStorage
const existingStatuses = localStorage.getItem(statusesKey);
const statuses = existingStatuses ? JSON.parse(existingStatuses) : {};

// Update the status for this expansion
statuses[expansionTxHashHex] =
DelegationV2StakingState.INTERMEDIATE_PENDING_BTC_CONFIRMATION;
Comment thread
gbarkhatov marked this conversation as resolved.

// Save back to localStorage
localStorage.setItem(statusesKey, JSON.stringify(statuses));
} catch (error) {
console.error("Failed to mark expansion as broadcasted:", error);
}
}

/**
* Get all broadcasted expansions from localStorage.
* Broadcasted expansions are those with INTERMEDIATE_PENDING_BTC_CONFIRMATION status.
*
* @param publicKeyNoCoord - The public key of the wallet
* @param expansions - The list of expansions to check against localStorage
* @returns Array of expansions that have been broadcasted
*/
export function getBroadcastedExpansions(
publicKeyNoCoord: string | undefined,
expansions: DelegationV2[],
): DelegationV2[] {
if (!publicKeyNoCoord || !expansions || expansions.length === 0) {
return [];
}

const storageKey = getExpansionsLocalStorageKey(publicKeyNoCoord);
const statusesKey = `${storageKey}_statuses`;

try {
// Get statuses from localStorage
const storedStatuses = localStorage.getItem(statusesKey);
if (!storedStatuses) {
return [];
}

const statuses = JSON.parse(storedStatuses);

// Filter expansions to only include those marked as broadcasted
return expansions.filter(
(expansion) =>
statuses[expansion.stakingTxHashHex] ===
DelegationV2StakingState.INTERMEDIATE_PENDING_BTC_CONFIRMATION,
);
} catch (error) {
console.error("Failed to get broadcasted expansions:", error);
return [];
}
}

/**
* Clean up a broadcasted expansion from localStorage when it becomes ACTIVE.
* This is called when an expansion transitions from broadcasted to confirmed on-chain.
*
* @param expansionTxHashHex - The transaction hash of the expansion to clean up
* @param publicKeyNoCoord - The public key of the wallet
*/
export function cleanupActiveExpansion(
expansionTxHashHex: string,
publicKeyNoCoord: string | undefined,
): void {
if (!publicKeyNoCoord) {
return;
}

const storageKey = getExpansionsLocalStorageKey(publicKeyNoCoord);
const pendingKey = `${storageKey}_pending`;
const statusesKey = `${storageKey}_statuses`;

// Remove from pending delegations
removeFromLocalStorageRecord(pendingKey, expansionTxHashHex);

// Remove from statuses
removeFromLocalStorageRecord(statusesKey, expansionTxHashHex);
}

/**
* Check if an expansion has been broadcasted.
* This is a convenience function that checks if the expansion has
* INTERMEDIATE_PENDING_BTC_CONFIRMATION status in localStorage.
*
* @param expansionTxHashHex - The transaction hash to check
* @param publicKeyNoCoord - The public key of the wallet
* @returns true if the expansion has been broadcasted, false otherwise
*/
export function isExpansionBroadcasted(
expansionTxHashHex: string,
publicKeyNoCoord: string | undefined,
): boolean {
if (!publicKeyNoCoord) {
return false;
}

const storageKey = getExpansionsLocalStorageKey(publicKeyNoCoord);
const statusesKey = `${storageKey}_statuses`;

try {
const statusesData = localStorage.getItem(statusesKey);
if (!statusesData) {
return false;
}

const statuses = JSON.parse(statusesData);
return (
statuses[expansionTxHashHex] ===
DelegationV2StakingState.INTERMEDIATE_PENDING_BTC_CONFIRMATION
);
} catch (error) {
console.error("Failed to check if expansion is broadcasted:", error);
return false;
}
}