Skip to content

Commit

Permalink
feat: add selegation storage
Browse files Browse the repository at this point in the history
  • Loading branch information
totraev committed Nov 29, 2024
1 parent 78ca065 commit 32c7fe3
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 76 deletions.
10 changes: 10 additions & 0 deletions src/app/components/Staking/Staking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
} from "@/app/hooks/services/useTransactionService";
import { useHealthCheck } from "@/app/hooks/useHealthCheck";
import { useAppState } from "@/app/state";
import { useDelegationV2State } from "@/app/state/DelegationV2State";
import { DelegationV2StakingState } from "@/app/types/delegationsV2";
import { ErrorHandlerParam, ErrorState } from "@/app/types/errors";
import {
FinalityProvider,
Expand Down Expand Up @@ -74,6 +76,7 @@ export const Staking = () => {
useLocalStorage<boolean>("bbn-staking-cancelFeedbackModalOpened ", false);

const { createDelegationEoi, estimateStakingFee } = useTransactionService();
const { addDelegation } = useDelegationV2State();
const { networkInfo } = useAppState();
const latestParam = networkInfo?.params.bbnStakingParams?.latestParam;
const stakingStatus = networkInfo?.stakingStatus;
Expand Down Expand Up @@ -226,6 +229,13 @@ export const Staking = () => {
signingCallback,
);

addDelegation({
stakingAmount: stakingAmountSat,
stakingTxHashHex,
startHeight: 0,
state: DelegationV2StakingState.INTERMEDIATE_PENDING_VERIFICATION,
});

setStakingTxHashHex(stakingTxHashHex);
setPendingVerificationOpen(true);
} catch (error: Error | any) {
Expand Down
31 changes: 29 additions & 2 deletions src/app/hooks/services/useDelegationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { useCallback, useMemo } from "react";

import { DELEGATION_ACTIONS as ACTIONS } from "@/app/constants";
import { useDelegationV2State } from "@/app/state/DelegationV2State";
import type { DelegationV2StakingState } from "@/app/types/delegationsV2";
import type {
DelegationLike,
DelegationV2StakingState,
} from "@/app/types/delegationsV2";

import { useTransactionService } from "./useTransactionService";

Expand Down Expand Up @@ -39,13 +42,15 @@ export function useDelegationService() {
hasMoreDelegations,
isLoading,
findDelegationByTxHash,
addDelegation,
} = useDelegationV2State();

const {
submitStakingTx,
submitUnbondingTx,
submitEarlyUnbondedWithdrawalTx,
submitTimelockUnbondedWithdrawalTx,
createDelegationEoi,
} = useTransactionService();

const COMMANDS: Record<ActionType, DelegationCommand> = useMemo(
Expand Down Expand Up @@ -140,7 +145,12 @@ export function useDelegationService() {
);
},
}),
[],
[
submitStakingTx,
submitUnbondingTx,
submitEarlyUnbondedWithdrawalTx,
submitTimelockUnbondedWithdrawalTx,
],
);

const executeDelegationAction = useCallback(
Expand Down Expand Up @@ -192,6 +202,23 @@ export function useDelegationService() {
[COMMANDS, findDelegationByTxHash],
);

const submitDelegationEOI = useCallback(
async ({
stakingAmount,
stakingTxHashHex,
startHeight,
state,
}: DelegationLike) => {
addDelegation({
stakingAmount,
stakingTxHashHex,
startHeight,
state,
});
},
[addDelegation, createDelegationEoi],
);

return {
isLoading,
delegations,
Expand Down
126 changes: 126 additions & 0 deletions src/app/hooks/storage/useDelegationStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { useCallback, useEffect, useMemo } from "react";
import { useLocalStorage } from "usehooks-ts";

import {
DELEGATION_STATUSES,
DelegationLike,
DelegationV2,
DelegationV2StakingState as State,
} from "@/app/types/delegationsV2";

export function useDelegationStorage(
key: string,
delegations?: DelegationV2[],
) {
console.log(delegations);
const [pendingDelegations = {}, setPendingDelegations] = useLocalStorage<
Record<string, DelegationLike>
>(`${key}_pending`, {});
const [delegationStatuses = {}, setDelegationStatuses] = useLocalStorage<
Record<string, State>
>(`${key}_statuses`, {});

const delegationMap = useMemo(() => {
return (delegations ?? []).reduce(
(acc, delegation) => ({
...acc,
[delegation.stakingTxHashHex]: delegation,
}),
{} as Record<string, DelegationV2>,
);
}, [delegations]);

const formattedDelegations = useMemo(() => {
const pendingDelegationArr = Object.values(pendingDelegations).map(
(d) =>
({
...d,
stakingTxHex: "",
paramsVersion: 0,
finalityProviderBtcPksHex: [],
stakerBtcPkHex: "",
stakingTime: 0,
endHeight: 0,
unbondingTime: 0,
unbondingTxHex: "",
}) as DelegationV2,
);

return pendingDelegationArr.concat(
(delegations ?? [])
.filter((d) => !pendingDelegations[d.stakingTxHashHex])
.map((d) => ({
...d,
state: delegationStatuses[d.stakingTxHashHex] ?? d.state,
})),
);
}, [delegations, pendingDelegations, delegationStatuses]);

useEffect(
function syncPendingDelegations() {
if (!key) return;

setPendingDelegations((delegations) => {
const result = Object.values(delegations)
.filter((d) => !delegationMap[d.stakingTxHashHex])
.reduce(
(acc, d) => ({ ...acc, [d.stakingTxHashHex]: d }),
{} as Record<string, DelegationLike>,
);

return result;
});
},
[key, delegationMap, setPendingDelegations],
);

useEffect(
function syncDelegationStatuses() {
if (!key) return;

setDelegationStatuses((statuses) =>
Object.entries(statuses)
.filter(
([hash, status]) =>
DELEGATION_STATUSES[status] <
DELEGATION_STATUSES[delegationMap[hash].state],
)
.reduce(
(acc, [hash, status]) => ({ ...acc, [hash]: status }),
{} as Record<string, State>,
),
);
},
[key, delegationMap, setDelegationStatuses],
);

const addPendingDelegation = useCallback(
(delegation: DelegationLike) => {
if (!key) return;

setPendingDelegations((delegations) => ({
...delegations,
[delegation.stakingTxHashHex]: {
...delegation,
state: State.INTERMEDIATE_PENDING_VERIFICATION,
},
}));
},
[key, setPendingDelegations],
);

const updateDelegationStatus = useCallback(
(id: string, status: State) => {
if (!key) return;

setDelegationStatuses((statuses) => ({ ...statuses, [id]: status }));
},
[key, setDelegationStatuses],
);

return {
delegations: formattedDelegations,
addPendingDelegation,
updateDelegationStatus,
};
}
81 changes: 19 additions & 62 deletions src/app/state/DelegationV2State.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { useCallback, useEffect, useMemo, type PropsWithChildren } from "react";
import { useLocalStorage } from "usehooks-ts";
import { useCallback, useMemo, type PropsWithChildren } from "react";

import { useBTCWallet } from "@/app/context/wallet/BTCWalletProvider";
import type { DelegationV2 } from "@/app/types/delegationsV2";
import type { DelegationLike, DelegationV2 } from "@/app/types/delegationsV2";
import { createStateUtils } from "@/utils/createStateUtils";
import { getDelegationsV2LocalStorageKey } from "@/utils/local_storage/getDelegationsLocalStorageKey";

import { getDelegationsV2LocalStorageKey } from "@/utils/local_storage/getDelegationsLocalStorageKey";
import { useDelegationsV2 } from "../hooks/api/useDelegationsV2";
import { useDelegationStorage } from "../hooks/storage/useDelegationStorage";

interface DelegationV2State {
isLoading: boolean;
hasMoreDelegations: boolean;
delegations: DelegationV2[];
addDelegation: (delegation: DelegationV2) => void;
addDelegation: (delegation: DelegationLike) => void;
updateDelegationStatus: (is: string, status: DelegationV2["state"]) => void;
fetchMoreDelegations: () => void;
findDelegationByTxHash: (txHash: string) => DelegationV2 | undefined;
}
Expand All @@ -21,8 +22,9 @@ const { StateProvider, useState } = createStateUtils<DelegationV2State>({
isLoading: false,
delegations: [],
hasMoreDelegations: false,
addDelegation: () => null,
fetchMoreDelegations: () => null,
addDelegation: () => {},
updateDelegationStatus: () => {},
fetchMoreDelegations: () => {},
findDelegationByTxHash: () => undefined,
});

Expand All @@ -32,43 +34,11 @@ export function DelegationV2State({ children }: PropsWithChildren) {
useDelegationsV2();

// States
const [delegations, setDelegations] = useLocalStorage<DelegationV2[]>(
getDelegationsV2LocalStorageKey(publicKeyNoCoord),
[],
);

// Effects
useEffect(
function syncDelegations() {
if (!data?.delegations) {
return;
}
// TODO: Find the difference and update only the difference
if (!areDelegationsEqual(delegations, data.delegations)) {
setDelegations(data.delegations);
}
},
[data?.delegations, delegations, setDelegations],
);

// Methods
const addDelegation = useCallback(
(newDelegation: DelegationV2) => {
setDelegations((delegations) => {
const exists = delegations.some(
(delegation) =>
delegation.stakingTxHashHex === newDelegation.stakingTxHashHex,
);

if (!exists) {
return [newDelegation, ...delegations];
}

return delegations;
});
},
[setDelegations],
);
const { delegations, addPendingDelegation, updateDelegationStatus } =
useDelegationStorage(
getDelegationsV2LocalStorageKey(publicKeyNoCoord),
data?.delegations,
);

// Get a delegation by its txHash
const findDelegationByTxHash = useCallback(
Expand All @@ -79,18 +49,20 @@ export function DelegationV2State({ children }: PropsWithChildren) {
// Context
const state = useMemo(
() => ({
delegations,
delegations: delegations,
isLoading: isFetchingNextPage,
hasMoreDelegations: hasNextPage,
addDelegation,
addDelegation: addPendingDelegation,
updateDelegationStatus,
findDelegationByTxHash,
fetchMoreDelegations: fetchNextPage,
}),
[
delegations,
isFetchingNextPage,
hasNextPage,
addDelegation,
addPendingDelegation,
updateDelegationStatus,
fetchNextPage,
findDelegationByTxHash,
],
Expand All @@ -100,18 +72,3 @@ export function DelegationV2State({ children }: PropsWithChildren) {
}

export const useDelegationV2State = useState;

function areDelegationsEqual(
arr1: DelegationV2[],
arr2: DelegationV2[],
): boolean {
if (arr1.length !== arr2.length) return false;

return arr1.every((item, index) => {
const other = arr2[index];
return (
item.stakingTxHashHex === other.stakingTxHashHex &&
item.state === other.state
);
});
}
Loading

0 comments on commit 32c7fe3

Please sign in to comment.