From cfaa174253a9d70f13b83ad36b51f92893272eb4 Mon Sep 17 00:00:00 2001 From: Jeremy <168515712+jeremy-babylonlabs@users.noreply.github.com> Date: Thu, 16 Jan 2025 19:22:38 +0800 Subject: [PATCH] phase 1 fp name (#633) --- src/app/api/getFinalityProviders.ts | 13 +-- src/app/api/getFinalityProvidersV2.ts | 99 +++++++++++++++++++ src/app/components/Delegations/Delegation.tsx | 16 +-- .../components/DelegationStatus.tsx | 33 ------- src/app/globals.css | 4 +- .../hooks/client/api/useFinalityProviders.ts | 2 +- .../client/api/useFinalityProvidersV2.ts | 67 +++++++++++++ src/app/state/FinalityProviderState.tsx | 41 +++++++- src/app/types/finalityProviders.ts | 11 +++ src/utils/getState.ts | 4 +- 10 files changed, 233 insertions(+), 57 deletions(-) create mode 100644 src/app/api/getFinalityProvidersV2.ts delete mode 100644 src/app/components/Delegations/components/DelegationStatus.tsx create mode 100644 src/app/hooks/client/api/useFinalityProvidersV2.ts diff --git a/src/app/api/getFinalityProviders.ts b/src/app/api/getFinalityProviders.ts index 2b67b778..27bf6ce2 100644 --- a/src/app/api/getFinalityProviders.ts +++ b/src/app/api/getFinalityProviders.ts @@ -1,15 +1,12 @@ import { isValidUrl } from "@/utils/url"; import { Pagination } from "../types/api"; -import { - FinalityProvider, - FinalityProviderState, -} from "../types/finalityProviders"; +import { FinalityProviderV1 } from "../types/finalityProviders"; import { apiWrapper } from "./apiWrapper"; export interface PaginatedFinalityProviders { - finalityProviders: FinalityProvider[]; + finalityProviders: FinalityProviderV1[]; pagination: Pagination; } @@ -20,7 +17,7 @@ interface FinalityProvidersAPIResponse { interface FinalityProviderAPI { description: DescriptionAPI; - state: FinalityProviderState; + state: "active" | "standby"; commission: string; btc_pk: string; active_tvl: number; @@ -60,7 +57,7 @@ export const getFinalityProviders = async ({ const response = await apiWrapper( "GET", - "/v2/finality-providers", + "/v1/finality-providers", "Error getting finality providers", { query: params }, ); @@ -71,7 +68,7 @@ export const getFinalityProviders = async ({ finalityProvidersAPIResponse.data; const finalityProviders = finalityProvidersAPI.map( - (fp: FinalityProviderAPI): FinalityProvider => ({ + (fp: FinalityProviderAPI): FinalityProviderV1 => ({ description: { moniker: fp.description.moniker, identity: fp.description.identity, diff --git a/src/app/api/getFinalityProvidersV2.ts b/src/app/api/getFinalityProvidersV2.ts new file mode 100644 index 00000000..d2e2b336 --- /dev/null +++ b/src/app/api/getFinalityProvidersV2.ts @@ -0,0 +1,99 @@ +import { isValidUrl } from "@/utils/url"; + +import { Pagination } from "../types/api"; +import { + FinalityProvider, + FinalityProviderState, +} from "../types/finalityProviders"; + +import { apiWrapper } from "./apiWrapper"; + +export interface PaginatedFinalityProviders { + finalityProviders: FinalityProvider[]; + pagination: Pagination; +} + +interface FinalityProvidersAPIResponse { + data: FinalityProviderAPI[]; + pagination: Pagination; +} + +interface FinalityProviderAPI { + description: DescriptionAPI; + state: FinalityProviderState; + commission: string; + btc_pk: string; + active_tvl: number; + total_tvl: number; + active_delegations: number; + total_delegations: number; +} + +interface DescriptionAPI { + moniker: string; + identity: string; + website: string; + security_contact: string; + details: string; +} + +export const getFinalityProvidersV2 = async ({ + key, + pk, + sortBy, + order, + name, +}: { + key: string; + name?: string; + sortBy?: string; + order?: "asc" | "desc"; + pk?: string; +}): Promise => { + const params = { + pagination_key: key, + finality_provider_pk: pk, + sort_by: sortBy, + order, + name, + }; + + const response = await apiWrapper( + "GET", + "/v2/finality-providers", + "Error getting finality providers", + { query: params }, + ); + + const finalityProvidersAPIResponse: FinalityProvidersAPIResponse = + response.data; + const finalityProvidersAPI: FinalityProviderAPI[] = + finalityProvidersAPIResponse.data; + + const finalityProviders = finalityProvidersAPI.map( + (fp: FinalityProviderAPI): FinalityProvider => ({ + description: { + moniker: fp.description.moniker, + identity: fp.description.identity, + website: isValidUrl(fp.description.website) + ? fp.description.website + : "", + securityContact: fp.description.security_contact, + details: fp.description.details, + }, + state: fp.state, + commission: fp.commission, + btcPk: fp.btc_pk, + activeTVLSat: fp.active_tvl, + totalTVLSat: fp.total_tvl, + activeDelegations: fp.active_delegations, + totalDelegations: fp.total_delegations, + }), + ); + + const pagination: Pagination = { + next_key: finalityProvidersAPIResponse.pagination.next_key, + }; + + return { finalityProviders, pagination }; +}; diff --git a/src/app/components/Delegations/Delegation.tsx b/src/app/components/Delegations/Delegation.tsx index c643fb89..713d5d9b 100644 --- a/src/app/components/Delegations/Delegation.tsx +++ b/src/app/components/Delegations/Delegation.tsx @@ -13,6 +13,7 @@ import { type Delegation as DelegationInterface, DelegationState, } from "@/app/types/delegations"; +import { Hint } from "@/components/common/Hint"; import { getNetworkConfigBTC } from "@/config/network/btc"; import { satoshiToBtc } from "@/utils/btc"; import { getState, getStateTooltip } from "@/utils/getState"; @@ -23,7 +24,6 @@ import { trim } from "@/utils/trim"; import { VerificationModal } from "../Modals/VerificationModal"; import { DelegationCell } from "./components/DelegationCell"; -import { DelegationStatus } from "./components/DelegationStatus"; interface DelegationProps { delegation: DelegationInterface; @@ -74,7 +74,7 @@ export const Delegation: React.FC = ({ resetRegistration: handleCloseRegistration, } = useDelegationState(); const { registerPhase1Delegation } = useRegistrationService(); - const { getFinalityProvider } = useFinalityProviderState(); + const { getFinalityProviderName } = useFinalityProviderState(); const { coinName, mempoolApiUrl } = getNetworkConfigBTC(); useEffect(() => { @@ -118,10 +118,9 @@ export const Delegation: React.FC = ({ - {getFinalityProvider(finalityProviderPkHex)?.description?.moniker ?? - "-"} + {getFinalityProviderName(finalityProviderPkHex) ?? "-"} = ({ order="order-5" className="relative flex justify-end lg:justify-start" > - + {renderState()} diff --git a/src/app/components/Delegations/components/DelegationStatus.tsx b/src/app/components/Delegations/components/DelegationStatus.tsx deleted file mode 100644 index 69c430c4..00000000 --- a/src/app/components/Delegations/components/DelegationStatus.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { AiOutlineInfoCircle } from "react-icons/ai"; -import { Tooltip } from "react-tooltip"; -import { twJoin } from "tailwind-merge"; - -interface DelegationStatusProps { - state: string; - tooltip: string; - stakingTxHashHex: string; - isOverflow?: boolean; -} - -export const DelegationStatus: React.FC = ({ - state, - tooltip, - stakingTxHashHex, - isOverflow, -}) => ( -
-

{state}

- - - - -
-); diff --git a/src/app/globals.css b/src/app/globals.css index 6783bce4..3d600ebb 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -104,8 +104,10 @@ body.modal-open { .tooltip-wrap { max-width: 25rem; - white-space: normal; + white-space: pre-wrap; word-wrap: break-word; + height: fit-content !important; + z-index: 9999; } .tooltip-wrap[data-popper-placement*="top"] .tooltip-arrow { diff --git a/src/app/hooks/client/api/useFinalityProviders.ts b/src/app/hooks/client/api/useFinalityProviders.ts index 3bab3656..13275276 100644 --- a/src/app/hooks/client/api/useFinalityProviders.ts +++ b/src/app/hooks/client/api/useFinalityProviders.ts @@ -9,7 +9,7 @@ import { ONE_MINUTE } from "@/app/constants"; import { useError } from "@/app/context/Error/ErrorContext"; import { ErrorState } from "@/app/types/errors"; -const FINALITY_PROVIDERS_KEY = "GET_FINALITY_PROVIDERS_V2_KEY"; +const FINALITY_PROVIDERS_KEY = "GET_FINALITY_PROVIDERS_V1_KEY"; interface Params { pk?: string; diff --git a/src/app/hooks/client/api/useFinalityProvidersV2.ts b/src/app/hooks/client/api/useFinalityProvidersV2.ts new file mode 100644 index 00000000..1917a7fe --- /dev/null +++ b/src/app/hooks/client/api/useFinalityProvidersV2.ts @@ -0,0 +1,67 @@ +import { useInfiniteQuery } from "@tanstack/react-query"; +import { useEffect } from "react"; + +import { + type PaginatedFinalityProviders, + getFinalityProvidersV2, +} from "@/app/api/getFinalityProvidersV2"; +import { ONE_MINUTE } from "@/app/constants"; +import { useError } from "@/app/context/Error/ErrorContext"; +import { ErrorState } from "@/app/types/errors"; + +const FINALITY_PROVIDERS_KEY = "GET_FINALITY_PROVIDERS_V2_KEY"; + +interface Params { + pk?: string; + name?: string; + sortBy?: string; + order?: "asc" | "desc"; +} + +export function useFinalityProvidersV2({ + pk, + sortBy, + order, + name, +}: Params = {}) { + const { isErrorOpen, handleError, captureError } = useError(); + + const query = useInfiniteQuery({ + queryKey: [FINALITY_PROVIDERS_KEY], + queryFn: ({ pageParam = "" }) => + getFinalityProvidersV2({ key: pageParam, pk, sortBy, order, name }), + getNextPageParam: (lastPage) => + lastPage?.pagination?.next_key !== "" + ? lastPage?.pagination?.next_key + : null, + initialPageParam: "", + refetchInterval: ONE_MINUTE, + placeholderData: (prev) => prev, + select: (data) => { + const flattenedData = data.pages.reduce( + (acc, page) => { + acc.finalityProviders.push(...page.finalityProviders); + acc.pagination = page.pagination; + return acc; + }, + { finalityProviders: [], pagination: { next_key: "" } }, + ); + return flattenedData; + }, + retry: (failureCount) => { + return !isErrorOpen && failureCount <= 3; + }, + }); + + useEffect(() => { + handleError({ + error: query.error, + hasError: query.isError, + errorState: ErrorState.SERVER_ERROR, + refetchFunction: query.refetch, + }); + captureError(query.error); + }, [query.isError, query.error, query.refetch, handleError, captureError]); + + return query; +} diff --git a/src/app/state/FinalityProviderState.tsx b/src/app/state/FinalityProviderState.tsx index a8f42509..34819e41 100644 --- a/src/app/state/FinalityProviderState.tsx +++ b/src/app/state/FinalityProviderState.tsx @@ -3,8 +3,10 @@ import { useSearchParams } from "next/navigation"; import { useCallback, useMemo, useState, type PropsWithChildren } from "react"; import { useFinalityProviders } from "@/app/hooks/client/api/useFinalityProviders"; +import { useFinalityProvidersV2 } from "@/app/hooks/client/api/useFinalityProvidersV2"; import { FinalityProviderState as FinalityProviderStateEnum, + FinalityProviderV1, type FinalityProvider, } from "@/app/types/finalityProviders"; import { createStateUtils } from "@/utils/createStateUtils"; @@ -30,6 +32,7 @@ interface FinalityProviderState { isRowSelectable: (row: FinalityProvider) => boolean; getFinalityProvider: (btcPkHex: string) => FinalityProvider | null; fetchNextPage: () => void; + getFinalityProviderName: (btcPkHex: string) => string | undefined; } const SORT_DIRECTIONS = { @@ -68,6 +71,7 @@ const defaultState: FinalityProviderState = { handleFilter: () => {}, getFinalityProvider: () => null, fetchNextPage: () => {}, + getFinalityProviderName: () => undefined, }; const { StateProvider, useState: useFpState } = @@ -85,12 +89,45 @@ export function FinalityProviderState({ children }: PropsWithChildren) { const debouncedSearch = useDebounce(filter.search, 300); const { data, hasNextPage, fetchNextPage, isFetching, isError } = - useFinalityProviders({ + useFinalityProvidersV2({ sortBy: sortState.field, order: sortState.direction, name: debouncedSearch, }); + const { data: dataV1 } = useFinalityProviders(); + + const providersMap = useMemo( + () => + (data?.finalityProviders ?? []).reduce((acc, fp) => { + if (fp.btcPk) { + acc.set(fp.btcPk, fp); + } + + return acc; + }, new Map()), + [data?.finalityProviders], + ); + + const providersV1Map = useMemo( + () => + (dataV1?.finalityProviders ?? []).reduce((acc, fp) => { + if (fp.btcPk) { + acc.set(fp.btcPk, fp); + } + + return acc; + }, new Map()), + [dataV1?.finalityProviders], + ); + + const getFinalityProviderName = useCallback( + (btcPkHex: string) => + providersMap.get(btcPkHex)?.description?.moniker ?? + providersV1Map.get(btcPkHex)?.description?.moniker, + [providersMap, providersV1Map], + ); + const handleFilter = useCallback((key: keyof FilterState, value: string) => { setFilter((state) => ({ ...state, [key]: value })); }, []); @@ -142,6 +179,7 @@ export function FinalityProviderState({ children }: PropsWithChildren) { isRowSelectable, getFinalityProvider, fetchNextPage, + getFinalityProviderName, }), [ filter, @@ -154,6 +192,7 @@ export function FinalityProviderState({ children }: PropsWithChildren) { isRowSelectable, getFinalityProvider, fetchNextPage, + getFinalityProviderName, ], ); diff --git a/src/app/types/finalityProviders.ts b/src/app/types/finalityProviders.ts index bdbe91e2..4a442033 100644 --- a/src/app/types/finalityProviders.ts +++ b/src/app/types/finalityProviders.ts @@ -9,6 +9,17 @@ export interface FinalityProvider { totalDelegations: number; } +export interface FinalityProviderV1 { + description: Description; + state: "active" | "standby"; + commission: string; + btcPk: string; + activeTVLSat: number; + totalTVLSat: number; + activeDelegations: number; + totalDelegations: number; +} + export interface Description { moniker: string; identity: string; diff --git a/src/utils/getState.ts b/src/utils/getState.ts index 8e442424..48fc1f1e 100644 --- a/src/utils/getState.ts +++ b/src/utils/getState.ts @@ -28,7 +28,7 @@ export const getState = (state: string) => { case DelegationState.INTERMEDIATE_WITHDRAWAL: return "Withdrawal Submitted"; case DelegationState.INTERMEDIATE_TRANSITIONING: - return "Transitioning"; + return "Active/Pending Registration"; case DelegationState.TRANSITIONED: return "Transitioned"; default: @@ -57,7 +57,7 @@ export const getStateTooltip = (state: string) => { case DelegationState.INTERMEDIATE_WITHDRAWAL: return "Withdrawal transaction pending confirmation on Bitcoin"; case DelegationState.INTERMEDIATE_TRANSITIONING: - return `Stake is transitioning to the ${bbnNetworkFullName} network`; + return `Stake is pending registration to the ${bbnNetworkFullName} network`; case DelegationState.TRANSITIONED: return `Stake has been transitioned to the ${bbnNetworkFullName} network`; default: