Skip to content

Commit

Permalink
add points feature
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremy-babylonlabs committed Sep 10, 2024
1 parent ac2bbd0 commit 21185c8
Show file tree
Hide file tree
Showing 10 changed files with 415 additions and 74 deletions.
85 changes: 85 additions & 0 deletions src/app/api/getDelegationPoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { encode } from "url-safe-base64";

import { Pagination } from "../types/api";

import { apiWrapper } from "./apiWrapper";

export interface DelegationPoints {
staking_tx_hash_hex: string;
staker: {
pk: string;
points: number;
};
finality_provider: {
pk: string;
points: number;
};
staking_height: number;
unbonding_height: number | null;
expiry_height: number;
}

export interface PaginatedDelegationsPoints {
data: DelegationPoints[];
pagination: Pagination;
}

export const getDelegationPoints = async (
paginationKey?: string,
stakerBtcPk?: string,
stakingTxHashHexes?: string[],
): Promise<PaginatedDelegationsPoints> => {
const params: Record<string, string | string[]> = {};

if (stakerBtcPk && stakingTxHashHexes && stakingTxHashHexes.length > 0) {
throw new Error(
"Only one of stakerBtcPk or stakingTxHashHexes should be provided",
);
}

if (
!stakerBtcPk &&
(!stakingTxHashHexes || stakingTxHashHexes.length === 0)
) {
throw new Error(
"Either stakerBtcPk or stakingTxHashHexes must be provided",
);
}

if (stakerBtcPk) {
params.staker_btc_pk = encode(stakerBtcPk);
}

let allDelegationPoints: DelegationPoints[] = [];
let nextPaginationKey = paginationKey;

do {
const currentParams = { ...params };
if (nextPaginationKey && nextPaginationKey !== "") {
currentParams.pagination_key = encode(nextPaginationKey);
}

if (stakingTxHashHexes && stakingTxHashHexes.length > 0) {
currentParams.staking_tx_hash_hex = stakingTxHashHexes.slice(0, 10);
stakingTxHashHexes = stakingTxHashHexes.slice(10);
}

const response = await apiWrapper(
"GET",
"/v1/points/staker/delegations",
"Error getting delegation points",
currentParams,
);

allDelegationPoints = allDelegationPoints.concat(response.data.data);
nextPaginationKey = response.data.pagination.next_key;
} while (
nextPaginationKey ||
(stakingTxHashHexes && stakingTxHashHexes.length > 0)
);

return {
data: allDelegationPoints,
pagination: { next_key: nextPaginationKey || "" },
};
};
28 changes: 28 additions & 0 deletions src/app/api/getStakersPoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { encode } from "url-safe-base64";

import { apiWrapper } from "./apiWrapper";

export interface StakerPoints {
staker_btc_pk: string;
points: number;
}

export const getStakersPoints = async (
stakerBtcPk: string[],
): Promise<StakerPoints[]> => {
const params: Record<string, string> = {};

params.staker_btc_pk =
stakerBtcPk.length > 1
? stakerBtcPk.map(encode).join(",")
: encode(stakerBtcPk[0]);

const response = await apiWrapper(
"GET",
"/v1/points/stakers",
"Error getting staker points",
params,
);

return response.data;
};
11 changes: 7 additions & 4 deletions src/app/components/Delegations/Delegation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { getState, getStateTooltip } from "@/utils/getState";
import { maxDecimals } from "@/utils/maxDecimals";
import { trim } from "@/utils/trim";

import { DelegationPoints } from "../Points/DelegationPoints";

interface DelegationProps {
stakingTx: StakingTx;
stakingValueSat: number;
Expand Down Expand Up @@ -122,7 +124,7 @@ export const Delegation: React.FC<DelegationProps> = ({
<p>overflow</p>
</div>
)}
<div className="grid grid-flow-col grid-cols-2 grid-rows-3 items-center gap-2 lg:grid-flow-row lg:grid-cols-5 lg:grid-rows-1">
<div className="grid grid-flow-col grid-cols-2 grid-rows-3 items-center gap-2 lg:grid-flow-row lg:grid-cols-6 lg:grid-rows-1">
<div className="flex gap-1 items-center order-1">
<FaBitcoin className="text-primary" />
<p>
Expand All @@ -142,13 +144,11 @@ export const Delegation: React.FC<DelegationProps> = ({
{trim(stakingTxHash)}
</a>
</div>
{/* Future data placeholder */}
<div className="order-5 lg:hidden" />
{/*
we need to center the text without the tooltip
add its size 12px and gap 4px, 16/2 = 8px
*/}
<div className="relative flex justify-end lg:left-[8px] lg:justify-center order-4">
<div className="relative flex justify-end lg:justify-center order-4">
<div className="flex items-center gap-1">
<p>{renderState()}</p>
<span
Expand All @@ -162,6 +162,9 @@ export const Delegation: React.FC<DelegationProps> = ({
<Tooltip id={`tooltip-${stakingTxHash}`} className="tooltip-wrap" />
</div>
</div>
<div className="relative flex justify-end lg:justify-center order-5">
<DelegationPoints stakingTxHash={stakingTxHash} />
</div>
<div className="order-6">{generateActionButton()}</div>
</div>
</div>
Expand Down
49 changes: 44 additions & 5 deletions src/app/components/Delegations/Delegations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useLocalStorage } from "usehooks-ts";

import { SignPsbtTransaction } from "@/app/common/utils/psbt";
import { LoadingTableList } from "@/app/components/Loading/Loading";
import { DelegationsPointsProvider } from "@/app/context/api/DelegationsPointsProvider";
import { useError } from "@/app/context/Error/ErrorContext";
import { QueryMeta } from "@/app/types/api";
import {
Expand Down Expand Up @@ -39,19 +40,56 @@ interface DelegationsProps {
pushTx: WalletProvider["pushTx"];
queryMeta: QueryMeta;
getNetworkFees: WalletProvider["getNetworkFees"];
isWalletConnected: boolean;
}

export const Delegations: React.FC<DelegationsProps> = ({
delegationsAPI,
delegationsLocalStorage,
globalParamsVersion,
publicKeyNoCoord,
btcWalletNetwork,
signPsbtTx,
pushTx,
queryMeta,
getNetworkFees,
address,
btcWalletNetwork,
publicKeyNoCoord,
isWalletConnected,
}) => {
return (
<DelegationsPointsProvider
publicKeyNoCoord={publicKeyNoCoord}
delegationsAPI={delegationsAPI}
isWalletConnected={isWalletConnected}
>
<DelegationsContent
delegationsAPI={delegationsAPI}
delegationsLocalStorage={delegationsLocalStorage}
globalParamsVersion={globalParamsVersion}
signPsbtTx={signPsbtTx}
pushTx={pushTx}
queryMeta={queryMeta}
getNetworkFees={getNetworkFees}
address={address}
btcWalletNetwork={btcWalletNetwork}
publicKeyNoCoord={publicKeyNoCoord}
isWalletConnected={isWalletConnected}
/>
</DelegationsPointsProvider>
);
};

const DelegationsContent: React.FC<DelegationsProps> = ({
delegationsAPI,
delegationsLocalStorage,
globalParamsVersion,
signPsbtTx,
pushTx,
queryMeta,
getNetworkFees,
address,
btcWalletNetwork,
publicKeyNoCoord,
}) => {
const [modalOpen, setModalOpen] = useState(false);
const [txID, setTxID] = useState("");
Expand Down Expand Up @@ -113,7 +151,7 @@ export const Delegations: React.FC<DelegationsProps> = ({
id,
delegationsAPI,
publicKeyNoCoord,
btcWalletNetwork,
btcWalletNetwork!,
signPsbtTx,
);
// Update the local state with the new intermediate delegation
Expand Down Expand Up @@ -143,7 +181,7 @@ export const Delegations: React.FC<DelegationsProps> = ({
id,
delegationsAPI,
publicKeyNoCoord,
btcWalletNetwork,
btcWalletNetwork!,
signPsbtTx,
address,
getNetworkFees,
Expand Down Expand Up @@ -233,11 +271,12 @@ export const Delegations: React.FC<DelegationsProps> = ({
</div>
) : (
<>
<div className="hidden grid-cols-5 gap-2 px-4 lg:grid">
<div className="hidden grid-cols-6 gap-2 px-4 lg:grid">
<p>Amount</p>
<p>Inception</p>
<p className="text-center">Transaction hash</p>
<p className="text-center">Status</p>
<p className="text-center">Points</p>
<p>Action</p>
</div>
<div
Expand Down
31 changes: 31 additions & 0 deletions src/app/components/Points/DelegationPoints.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from "react";

import { useDelegationsPoints } from "@/app/context/api/DelegationsPointsProvider";

interface DelegationPointsProps {
stakingTxHash: string;
}

export const DelegationPoints: React.FC<DelegationPointsProps> = ({
stakingTxHash,
}) => {
const { delegationPoints, isLoading } = useDelegationsPoints();

const points = delegationPoints.get(stakingTxHash);

if (isLoading) {
return (
<div className="flex items-center justify-end gap-1">
<div className="h-5 w-12 animate-pulse rounded bg-gray-300 dark:bg-gray-700"></div>
</div>
);
}

return (
<div className="flex items-center justify-end gap-1">
<p className="whitespace-nowrap font-semibold">
{points !== undefined ? points : 0}
</p>
</div>
);
};
39 changes: 39 additions & 0 deletions src/app/components/Points/StakerPoints.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useQuery } from "@tanstack/react-query";
import React from "react";

import { getStakersPoints } from "@/app/api/getStakersPoints";
import { satoshiToBtc } from "@/utils/btcConversions";
import { maxDecimals } from "@/utils/maxDecimals";

interface StakerPointsProps {
publicKeyNoCoord: string;
}

export const StakerPoints: React.FC<StakerPointsProps> = ({
publicKeyNoCoord,
}) => {
const { data: stakerPoints, isLoading } = useQuery({
queryKey: ["stakerPoints", publicKeyNoCoord],
queryFn: () => getStakersPoints([publicKeyNoCoord]),
enabled: !!publicKeyNoCoord,
refetchInterval: 60000, // Refresh every minute
});

if (isLoading) {
return (
<div className="flex items-center justify-end gap-1">
<div className="h-5 w-20 animate-pulse rounded bg-gray-300 dark:bg-gray-700"></div>
</div>
);
}

const points = stakerPoints?.[0]?.points;

return (
<div className="flex items-center justify-end gap-1">
<p className="whitespace-nowrap font-semibold">
{points !== undefined ? maxDecimals(satoshiToBtc(points), 8) : 0}
</p>
</div>
);
};
12 changes: 6 additions & 6 deletions src/app/components/Staking/Staking.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Transaction, networks } from "bitcoinjs-lib";
import { networks, Transaction } from "bitcoinjs-lib";
import { Dispatch, SetStateAction, useEffect, useMemo, useState } from "react";
import { Tooltip } from "react-tooltip";
import { useLocalStorage } from "usehooks-ts";
Expand Down Expand Up @@ -27,8 +27,8 @@ import {
} from "@/utils/delegations/signStakingTx";
import { getFeeRateFromMempool } from "@/utils/getFeeRateFromMempool";
import {
ParamsWithContext,
getCurrentGlobalParamsVersion,
ParamsWithContext,
} from "@/utils/globalParams";
import { isStakingSignReady } from "@/utils/isStakingSignReady";
import { toLocalStorageDelegation } from "@/utils/local_storage/toLocalStorageDelegation";
Expand Down Expand Up @@ -74,13 +74,13 @@ export const Staking: React.FC<StakingProps> = ({
isWalletConnected,
onConnect,
isLoading,
btcWallet,
btcWalletNetwork,
address,
publicKeyNoCoord,
setDelegationsLocalStorage,
btcWalletBalanceSat,
availableUTXOs,
publicKeyNoCoord,
address,
btcWallet,
btcWalletNetwork,
}) => {
// Staking form state
const [stakingAmountSat, setStakingAmountSat] = useState(0);
Expand Down
Loading

0 comments on commit 21185c8

Please sign in to comment.