Skip to content

Commit 0a9883e

Browse files
resolve comments
1 parent ac2bbd0 commit 0a9883e

10 files changed

+417
-74
lines changed

src/app/api/getDelegationPoints.ts

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { encode } from "url-safe-base64";
2+
3+
import { Pagination } from "../types/api";
4+
5+
import { apiWrapper } from "./apiWrapper";
6+
7+
export interface DelegationPoints {
8+
staking_tx_hash_hex: string;
9+
staker: {
10+
pk: string;
11+
points: number;
12+
};
13+
finality_provider: {
14+
pk: string;
15+
points: number;
16+
};
17+
staking_height: number;
18+
unbonding_height: number | null;
19+
expiry_height: number;
20+
}
21+
22+
export interface PaginatedDelegationsPoints {
23+
data: DelegationPoints[];
24+
pagination: Pagination;
25+
}
26+
27+
export const getDelegationPoints = async (
28+
paginationKey?: string,
29+
stakerBtcPk?: string,
30+
stakingTxHashHexes?: string[],
31+
): Promise<PaginatedDelegationsPoints> => {
32+
const params: Record<string, string | string[]> = {};
33+
34+
if (stakerBtcPk && stakingTxHashHexes && stakingTxHashHexes.length > 0) {
35+
throw new Error(
36+
"Only one of stakerBtcPk or stakingTxHashHexes should be provided",
37+
);
38+
}
39+
40+
if (
41+
!stakerBtcPk &&
42+
(!stakingTxHashHexes || stakingTxHashHexes.length === 0)
43+
) {
44+
throw new Error(
45+
"Either stakerBtcPk or stakingTxHashHexes must be provided",
46+
);
47+
}
48+
49+
if (stakerBtcPk) {
50+
params.staker_btc_pk = encode(stakerBtcPk);
51+
}
52+
53+
let allDelegationPoints: DelegationPoints[] = [];
54+
let nextPaginationKey = paginationKey;
55+
56+
do {
57+
const currentParams = { ...params };
58+
if (nextPaginationKey && nextPaginationKey !== "") {
59+
currentParams.pagination_key = encode(nextPaginationKey);
60+
}
61+
62+
if (stakingTxHashHexes && stakingTxHashHexes.length > 0) {
63+
currentParams.staking_tx_hash_hex = stakingTxHashHexes.slice(0, 10);
64+
stakingTxHashHexes = stakingTxHashHexes.slice(10);
65+
}
66+
67+
const response = await apiWrapper(
68+
"GET",
69+
"/v1/points/staker/delegations",
70+
"Error getting delegation points",
71+
currentParams,
72+
);
73+
74+
allDelegationPoints = allDelegationPoints.concat(response.data.data);
75+
nextPaginationKey = response.data.pagination.next_key;
76+
} while (
77+
nextPaginationKey ||
78+
(stakingTxHashHexes && stakingTxHashHexes.length > 0)
79+
);
80+
81+
return {
82+
data: allDelegationPoints,
83+
pagination: { next_key: nextPaginationKey || "" },
84+
};
85+
};

src/app/api/getStakersPoints.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { encode } from "url-safe-base64";
2+
3+
import { apiWrapper } from "./apiWrapper";
4+
5+
export interface StakerPoints {
6+
staker_btc_pk: string;
7+
points: number;
8+
}
9+
10+
export const getStakersPoints = async (
11+
stakerBtcPk: string[],
12+
): Promise<StakerPoints[]> => {
13+
const params: Record<string, string> = {};
14+
15+
params.staker_btc_pk =
16+
stakerBtcPk.length > 1
17+
? stakerBtcPk.map(encode).join(",")
18+
: encode(stakerBtcPk[0]);
19+
20+
const response = await apiWrapper(
21+
"GET",
22+
"/v1/points/stakers",
23+
"Error getting staker points",
24+
params,
25+
);
26+
27+
return response.data;
28+
};

src/app/components/Delegations/Delegation.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { getState, getStateTooltip } from "@/utils/getState";
1313
import { maxDecimals } from "@/utils/maxDecimals";
1414
import { trim } from "@/utils/trim";
1515

16+
import { DelegationPoints } from "../Points/DelegationPoints";
17+
1618
interface DelegationProps {
1719
stakingTx: StakingTx;
1820
stakingValueSat: number;
@@ -122,7 +124,7 @@ export const Delegation: React.FC<DelegationProps> = ({
122124
<p>overflow</p>
123125
</div>
124126
)}
125-
<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">
127+
<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">
126128
<div className="flex gap-1 items-center order-1">
127129
<FaBitcoin className="text-primary" />
128130
<p>
@@ -142,13 +144,11 @@ export const Delegation: React.FC<DelegationProps> = ({
142144
{trim(stakingTxHash)}
143145
</a>
144146
</div>
145-
{/* Future data placeholder */}
146-
<div className="order-5 lg:hidden" />
147147
{/*
148148
we need to center the text without the tooltip
149149
add its size 12px and gap 4px, 16/2 = 8px
150150
*/}
151-
<div className="relative flex justify-end lg:left-[8px] lg:justify-center order-4">
151+
<div className="relative flex justify-end lg:justify-center order-4">
152152
<div className="flex items-center gap-1">
153153
<p>{renderState()}</p>
154154
<span
@@ -162,6 +162,9 @@ export const Delegation: React.FC<DelegationProps> = ({
162162
<Tooltip id={`tooltip-${stakingTxHash}`} className="tooltip-wrap" />
163163
</div>
164164
</div>
165+
<div className="relative flex justify-end lg:justify-center order-5">
166+
<DelegationPoints stakingTxHash={stakingTxHash} />
167+
</div>
165168
<div className="order-6">{generateActionButton()}</div>
166169
</div>
167170
</div>

src/app/components/Delegations/Delegations.tsx

+44-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { useLocalStorage } from "usehooks-ts";
55

66
import { SignPsbtTransaction } from "@/app/common/utils/psbt";
77
import { LoadingTableList } from "@/app/components/Loading/Loading";
8+
import { DelegationsPointsProvider } from "@/app/context/api/DelegationsPointsProvider";
89
import { useError } from "@/app/context/Error/ErrorContext";
910
import { QueryMeta } from "@/app/types/api";
1011
import {
@@ -39,19 +40,56 @@ interface DelegationsProps {
3940
pushTx: WalletProvider["pushTx"];
4041
queryMeta: QueryMeta;
4142
getNetworkFees: WalletProvider["getNetworkFees"];
43+
isWalletConnected: boolean;
4244
}
4345

4446
export const Delegations: React.FC<DelegationsProps> = ({
4547
delegationsAPI,
4648
delegationsLocalStorage,
4749
globalParamsVersion,
48-
publicKeyNoCoord,
49-
btcWalletNetwork,
50+
signPsbtTx,
51+
pushTx,
52+
queryMeta,
53+
getNetworkFees,
5054
address,
55+
btcWalletNetwork,
56+
publicKeyNoCoord,
57+
isWalletConnected,
58+
}) => {
59+
return (
60+
<DelegationsPointsProvider
61+
publicKeyNoCoord={publicKeyNoCoord}
62+
delegationsAPI={delegationsAPI}
63+
isWalletConnected={isWalletConnected}
64+
>
65+
<DelegationsContent
66+
delegationsAPI={delegationsAPI}
67+
delegationsLocalStorage={delegationsLocalStorage}
68+
globalParamsVersion={globalParamsVersion}
69+
signPsbtTx={signPsbtTx}
70+
pushTx={pushTx}
71+
queryMeta={queryMeta}
72+
getNetworkFees={getNetworkFees}
73+
address={address}
74+
btcWalletNetwork={btcWalletNetwork}
75+
publicKeyNoCoord={publicKeyNoCoord}
76+
isWalletConnected={isWalletConnected}
77+
/>
78+
</DelegationsPointsProvider>
79+
);
80+
};
81+
82+
const DelegationsContent: React.FC<DelegationsProps> = ({
83+
delegationsAPI,
84+
delegationsLocalStorage,
85+
globalParamsVersion,
5186
signPsbtTx,
5287
pushTx,
5388
queryMeta,
5489
getNetworkFees,
90+
address,
91+
btcWalletNetwork,
92+
publicKeyNoCoord,
5593
}) => {
5694
const [modalOpen, setModalOpen] = useState(false);
5795
const [txID, setTxID] = useState("");
@@ -113,7 +151,7 @@ export const Delegations: React.FC<DelegationsProps> = ({
113151
id,
114152
delegationsAPI,
115153
publicKeyNoCoord,
116-
btcWalletNetwork,
154+
btcWalletNetwork!,
117155
signPsbtTx,
118156
);
119157
// Update the local state with the new intermediate delegation
@@ -143,7 +181,7 @@ export const Delegations: React.FC<DelegationsProps> = ({
143181
id,
144182
delegationsAPI,
145183
publicKeyNoCoord,
146-
btcWalletNetwork,
184+
btcWalletNetwork!,
147185
signPsbtTx,
148186
address,
149187
getNetworkFees,
@@ -233,11 +271,12 @@ export const Delegations: React.FC<DelegationsProps> = ({
233271
</div>
234272
) : (
235273
<>
236-
<div className="hidden grid-cols-5 gap-2 px-4 lg:grid">
274+
<div className="hidden grid-cols-6 gap-2 px-4 lg:grid">
237275
<p>Amount</p>
238276
<p>Inception</p>
239277
<p className="text-center">Transaction hash</p>
240278
<p className="text-center">Status</p>
279+
<p className="text-center">Points</p>
241280
<p>Action</p>
242281
</div>
243282
<div
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from "react";
2+
3+
import { useDelegationsPoints } from "@/app/context/api/DelegationsPointsProvider";
4+
5+
interface DelegationPointsProps {
6+
stakingTxHash: string;
7+
}
8+
9+
export const DelegationPoints: React.FC<DelegationPointsProps> = ({
10+
stakingTxHash,
11+
}) => {
12+
const { delegationPoints, isLoading } = useDelegationsPoints();
13+
14+
const points = delegationPoints.get(stakingTxHash);
15+
16+
if (isLoading) {
17+
return (
18+
<div className="flex items-center justify-end gap-1">
19+
<div className="h-5 w-12 animate-pulse rounded bg-gray-300 dark:bg-gray-700"></div>
20+
</div>
21+
);
22+
}
23+
24+
return (
25+
<div className="flex items-center justify-end gap-1">
26+
<p className="whitespace-nowrap font-semibold">
27+
{points !== undefined ? points : 0}
28+
</p>
29+
</div>
30+
);
31+
};
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import React from "react";
3+
4+
import { getStakersPoints } from "@/app/api/getStakersPoints";
5+
import { satoshiToBtc } from "@/utils/btcConversions";
6+
import { maxDecimals } from "@/utils/maxDecimals";
7+
8+
interface StakerPointsProps {
9+
publicKeyNoCoord: string;
10+
}
11+
12+
export const StakerPoints: React.FC<StakerPointsProps> = ({
13+
publicKeyNoCoord,
14+
}) => {
15+
const { data: stakerPoints, isLoading } = useQuery({
16+
queryKey: ["stakerPoints", publicKeyNoCoord],
17+
queryFn: () => getStakersPoints([publicKeyNoCoord]),
18+
enabled: !!publicKeyNoCoord,
19+
refetchInterval: 60000, // Refresh every minute
20+
});
21+
22+
if (isLoading) {
23+
return (
24+
<div className="flex items-center justify-end gap-1">
25+
<div className="h-5 w-20 animate-pulse rounded bg-gray-300 dark:bg-gray-700"></div>
26+
</div>
27+
);
28+
}
29+
30+
const points = stakerPoints?.[0]?.points;
31+
32+
return (
33+
<div className="flex items-center justify-end gap-1">
34+
<p className="whitespace-nowrap font-semibold">
35+
{points !== undefined ? maxDecimals(satoshiToBtc(points), 8) : 0}
36+
</p>
37+
</div>
38+
);
39+
};

src/app/components/Staking/Staking.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useQuery, useQueryClient } from "@tanstack/react-query";
2-
import { Transaction, networks } from "bitcoinjs-lib";
2+
import { networks, Transaction } from "bitcoinjs-lib";
33
import { Dispatch, SetStateAction, useEffect, useMemo, useState } from "react";
44
import { Tooltip } from "react-tooltip";
55
import { useLocalStorage } from "usehooks-ts";
@@ -27,8 +27,8 @@ import {
2727
} from "@/utils/delegations/signStakingTx";
2828
import { getFeeRateFromMempool } from "@/utils/getFeeRateFromMempool";
2929
import {
30-
ParamsWithContext,
3130
getCurrentGlobalParamsVersion,
31+
ParamsWithContext,
3232
} from "@/utils/globalParams";
3333
import { isStakingSignReady } from "@/utils/isStakingSignReady";
3434
import { toLocalStorageDelegation } from "@/utils/local_storage/toLocalStorageDelegation";
@@ -74,13 +74,13 @@ export const Staking: React.FC<StakingProps> = ({
7474
isWalletConnected,
7575
onConnect,
7676
isLoading,
77-
btcWallet,
78-
btcWalletNetwork,
79-
address,
80-
publicKeyNoCoord,
8177
setDelegationsLocalStorage,
8278
btcWalletBalanceSat,
8379
availableUTXOs,
80+
publicKeyNoCoord,
81+
address,
82+
btcWallet,
83+
btcWalletNetwork,
8484
}) => {
8585
// Staking form state
8686
const [stakingAmountSat, setStakingAmountSat] = useState(0);

0 commit comments

Comments
 (0)