Skip to content
This repository was archived by the owner on Sep 8, 2025. It is now read-only.

Commit e1ba352

Browse files
feat: bsn fp allowlist
1 parent f25ac86 commit e1ba352

7 files changed

Lines changed: 189 additions & 16 deletions

File tree

src/ui/common/api/getBsn.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,91 @@
1+
import { getNetworkConfigBBN } from "@/ui/common/config/network/bbn";
2+
13
import { Bsn } from "../types/bsn";
24
import { getBsnLogoUrl } from "../utils/bsnLogo";
35

46
import { apiWrapper } from "./apiWrapper";
57

8+
export type BsnType = "COSMOS" | "ROLLUP";
9+
10+
export const BSN_TYPE_COSMOS: BsnType = "COSMOS";
11+
export const BSN_TYPE_ROLLUP: BsnType = "ROLLUP";
12+
13+
const { chainId: BBN_CHAIN_ID } = getNetworkConfigBBN();
14+
15+
// BSN Configuration for different types and behaviors
16+
export interface BsnConfig {
17+
isBabylonGenesis: boolean;
18+
displayName: string;
19+
modalTitle: string;
20+
filterLabels: {
21+
primary: string;
22+
secondary: string;
23+
};
24+
showAllowlistFilter: boolean;
25+
fpFilterBehavior: "active-inactive" | "allowlist-based";
26+
}
27+
28+
export const BSN_CONFIGS: Record<string, BsnConfig> = {
29+
// Babylon Genesis
30+
[BBN_CHAIN_ID]: {
31+
isBabylonGenesis: true,
32+
displayName: "Babylon Genesis",
33+
modalTitle: "Select Babylon Genesis Finality Provider",
34+
filterLabels: {
35+
primary: "Active",
36+
secondary: "Inactive",
37+
},
38+
showAllowlistFilter: false,
39+
fpFilterBehavior: "active-inactive",
40+
},
41+
// Cosmos BSN config
42+
COSMOS: {
43+
isBabylonGenesis: false,
44+
displayName: "Cosmos",
45+
modalTitle: "Select Cosmos Finality Provider",
46+
filterLabels: {
47+
primary: "Active",
48+
secondary: "Inactive",
49+
},
50+
showAllowlistFilter: false,
51+
fpFilterBehavior: "active-inactive",
52+
},
53+
// Roll-up BSN config
54+
ROLLUP: {
55+
isBabylonGenesis: false,
56+
displayName: "Roll Up",
57+
modalTitle: "Select Roll Up Finality Provider",
58+
filterLabels: {
59+
primary: "Allow listed",
60+
secondary: "Non-Allow listed",
61+
},
62+
showAllowlistFilter: false, // Main filter handles this
63+
fpFilterBehavior: "allowlist-based",
64+
},
65+
};
66+
67+
/**
68+
* Get BSN configuration based on BSN object
69+
*/
70+
export const getBsnConfig = (bsn?: Bsn): BsnConfig => {
71+
if (!bsn) return BSN_CONFIGS.COSMOS;
72+
73+
// Check if it's Babylon Genesis first
74+
if (bsn.id === BBN_CHAIN_ID) {
75+
return BSN_CONFIGS[BBN_CHAIN_ID];
76+
}
77+
78+
// Use type-based config
79+
return BSN_CONFIGS[bsn.type] || BSN_CONFIGS.COSMOS;
80+
};
81+
682
interface BsnAPI {
783
id: string;
884
name: string;
985
description: string;
1086
active_tvl: number;
87+
type: BsnType;
88+
allowlist?: string[]; // BTC FP pubkeys (hex)
1189
}
1290

1391
interface BsnDataResponse {
@@ -20,8 +98,26 @@ const createBSN = (bsn: BsnAPI): Bsn => ({
2098
description: bsn.description,
2199
activeTvl: bsn.active_tvl,
22100
logoUrl: getBsnLogoUrl(bsn.id),
101+
type: bsn.type,
102+
allowlist: bsn.allowlist,
23103
});
24104

105+
/**
106+
* Determines if a BSN is a Roll-up BSN based on type field
107+
*/
108+
export const isRollupBsn = (bsn: Bsn): boolean => bsn.type === BSN_TYPE_ROLLUP;
109+
110+
/**
111+
* Determines if a BSN is a Cosmos BSN based on type field
112+
*/
113+
export const isCosmosBsn = (bsn: Bsn): boolean => bsn.type === BSN_TYPE_COSMOS;
114+
115+
/**
116+
* Determines if a BSN is Babylon Genesis
117+
*/
118+
export const isBabylonGenesisBsn = (bsn: Bsn): boolean =>
119+
bsn.id === BBN_CHAIN_ID;
120+
25121
export const getBSNs = async (): Promise<Bsn[]> => {
26122
const response = await apiWrapper<BsnDataResponse>(
27123
"GET",

src/ui/common/api/getFinalityProvidersV2.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ interface FinalityProviderAPI {
3131
logo_url?: string;
3232
bsn_id?: string;
3333
type: string;
34-
is_allowlisted?: boolean;
3534
}
3635

3736
interface DescriptionAPI {
@@ -102,7 +101,6 @@ export const getFinalityProvidersV2 = async ({
102101
bsnId: fp.bsn_id,
103102
bsnLogoUrl: getBsnLogoUrl(fp.bsn_id),
104103
chain: fp.type,
105-
isAllowlisted: fp.is_allowlisted,
106104
}),
107105
);
108106

src/ui/common/components/Multistaking/FinalityProviderField/FinalityProviderModal.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import {
44
DialogFooter,
55
DialogHeader,
66
} from "@babylonlabs-io/core-ui";
7-
import { useState } from "react";
7+
import { useMemo, useState } from "react";
88

9+
import { getBsnConfig } from "@/ui/common/api/getBsn";
910
import { ResponsiveDialog } from "@/ui/common/components/Modals/ResponsiveDialog";
1011
import { FinalityProviders } from "@/ui/common/components/Multistaking/FinalityProviderField/FinalityProviders";
12+
import { useFinalityProviderBsnState } from "@/ui/common/state/FinalityProviderBsnState";
1113

1214
interface Props {
1315
open: boolean;
@@ -27,6 +29,9 @@ export const FinalityProviderModal = ({
2729
onBack,
2830
}: Props) => {
2931
const [selectedFP, setSelectedFp] = useState(defaultFinalityProvider);
32+
const { selectedBsn } = useFinalityProviderBsnState();
33+
34+
const bsnConfig = useMemo(() => getBsnConfig(selectedBsn), [selectedBsn]);
3035

3136
const handleClose = () => {
3237
onClose();
@@ -36,7 +41,7 @@ export const FinalityProviderModal = ({
3641
return (
3742
<ResponsiveDialog open={open} onClose={handleClose} className="w-[52rem]">
3843
<DialogHeader
39-
title="Select Finality Provider"
44+
title={bsnConfig.modalTitle}
4045
onClose={handleClose}
4146
className="text-accent-primary"
4247
/>

src/ui/common/components/Multistaking/FinalityProviders/FinalityProviderAllowlistFilter.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { Select } from "@babylonlabs-io/core-ui";
2+
import { useMemo } from "react";
23

4+
import { getBsnConfig } from "@/ui/common/api/getBsn";
35
import { useFinalityProviderBsnState } from "@/ui/common/state/FinalityProviderBsnState";
46

57
const options = [
@@ -9,10 +11,12 @@ const options = [
911
];
1012

1113
export const FinalityProviderAllowlistFilter = () => {
12-
const { filter, handleFilter, selectedBsnId } = useFinalityProviderBsnState();
14+
const { filter, handleFilter, selectedBsn } = useFinalityProviderBsnState();
1315

14-
// Only show allowlist filter for non-Babylon BSNs
15-
if (!selectedBsnId || selectedBsnId === "babylon") {
16+
const bsnConfig = useMemo(() => getBsnConfig(selectedBsn), [selectedBsn]);
17+
18+
// Only show allowlist filter when config allows it
19+
if (!selectedBsn || !bsnConfig.showAllowlistFilter) {
1620
return null;
1721
}
1822

src/ui/common/components/Multistaking/FinalityProviders/FinalityProviderFilter.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
import { Select } from "@babylonlabs-io/core-ui";
2+
import { useMemo } from "react";
23

4+
import { getBsnConfig } from "@/ui/common/api/getBsn";
35
import { useFinalityProviderBsnState } from "@/ui/common/state/FinalityProviderBsnState";
46

5-
const options = [
6-
{ value: "active", label: "Active" },
7-
{ value: "inactive", label: "Inactive" },
8-
];
9-
107
export const FinalityProviderFilter = () => {
11-
const { filter, handleFilter } = useFinalityProviderBsnState();
8+
const { filter, handleFilter, selectedBsn } = useFinalityProviderBsnState();
9+
10+
const bsnConfig = useMemo(() => getBsnConfig(selectedBsn), [selectedBsn]);
11+
12+
const options = useMemo(
13+
() => [
14+
{ value: "active", label: bsnConfig.filterLabels.primary },
15+
{ value: "inactive", label: bsnConfig.filterLabels.secondary },
16+
],
17+
[bsnConfig.filterLabels],
18+
);
1219

1320
return (
1421
<Select

src/ui/common/state/FinalityProviderBsnState.tsx

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useDebounce } from "@uidotdev/usehooks";
22
import { useCallback, useMemo, useState, type PropsWithChildren } from "react";
33
import { useSearchParams } from "react-router";
44

5+
import { BSN_TYPE_COSMOS, getBsnConfig } from "@/ui/common/api/getBsn";
56
import { getNetworkConfigBBN } from "@/ui/common/config/network/bbn";
67
import { useBsn } from "@/ui/common/hooks/client/api/useBsn";
78
import { useFinalityProvidersV2 } from "@/ui/common/hooks/client/api/useFinalityProvidersV2";
@@ -14,6 +15,9 @@ import { createStateUtils } from "@/ui/common/utils/createStateUtils";
1415

1516
import { getBsnLogoUrl } from "../utils/bsnLogo";
1617

18+
const normalizeHex = (hex?: string): string =>
19+
(hex ?? "").trim().toLowerCase().replace(/^0x/, "");
20+
1721
interface SortState {
1822
field?: string;
1923
direction?: "asc" | "desc";
@@ -32,6 +36,7 @@ const defaultBabylonBsn: Bsn = {
3236
name: "Babylon Genesis",
3337
description: "Babylon Genesis",
3438
logoUrl: getBsnLogoUrl(BBN_CHAIN_ID),
39+
type: BSN_TYPE_COSMOS,
3540
};
3641

3742
interface FinalityProviderBsnState {
@@ -46,6 +51,7 @@ interface FinalityProviderBsnState {
4651
bsnLoading: boolean;
4752
bsnError: boolean;
4853
selectedBsnId: string | undefined;
54+
selectedBsn: Bsn | undefined;
4955
setSelectedBsnId: (id: string | undefined) => void;
5056
// Modal
5157
stakingModalPage: StakingModalPage;
@@ -119,6 +125,7 @@ const defaultState: FinalityProviderBsnState = {
119125
bsnLoading: false,
120126
bsnError: false,
121127
selectedBsnId: undefined,
128+
selectedBsn: undefined,
122129
setSelectedBsnId: () => {},
123130
isRowSelectable: () => false,
124131
handleSort: () => {},
@@ -226,11 +233,63 @@ export function FinalityProviderBsnState({ children }: PropsWithChildren) {
226233
[selectedBsnId],
227234
);
228235

236+
const selectedBsn = useMemo(
237+
() => bsnList.find((bsn) => bsn.id === selectedBsnId),
238+
[bsnList, selectedBsnId],
239+
);
240+
229241
const filteredFinalityProviders = useMemo(() => {
230-
return finalityProviders.filter((fp: FinalityProvider) =>
231-
Object.values(FILTERS).every((filterFn) => filterFn(fp, filter)),
242+
// First apply search filter
243+
let filtered = finalityProviders.filter((fp: FinalityProvider) =>
244+
FILTERS.searchTerm(fp, filter),
232245
);
233-
}, [finalityProviders, filter]);
246+
247+
// If search is active, return all matching results without status filtering
248+
if (filter.searchTerm) {
249+
return filtered;
250+
}
251+
252+
// Apply BSN-aware filtering based on provider status
253+
if (filter.providerStatus) {
254+
const bsnConfig = getBsnConfig(selectedBsn);
255+
if (bsnConfig.fpFilterBehavior === "allowlist-based") {
256+
// For rollup BSNs: filter all FPs by allowlist membership (regardless of active/inactive state)
257+
const allowSet = new Set(
258+
(selectedBsn?.allowlist || []).map(normalizeHex),
259+
);
260+
261+
filtered = filtered.filter((fp) => {
262+
const isAllowlisted = allowSet.has(normalizeHex(fp.btcPk));
263+
264+
if (filter.providerStatus === "active") {
265+
return isAllowlisted;
266+
} else {
267+
return !isAllowlisted;
268+
}
269+
});
270+
} else {
271+
// For cosmos/babylon BSNs: filter by active/inactive status
272+
const statusFilter =
273+
STATUS_FILTERS[filter.providerStatus as keyof typeof STATUS_FILTERS];
274+
if (statusFilter) {
275+
filtered = filtered.filter(statusFilter);
276+
}
277+
}
278+
}
279+
280+
// Apply allowlist filter
281+
if (filter.allowlistStatus) {
282+
const allowlistFilter =
283+
ALLOWLIST_FILTERS[
284+
filter.allowlistStatus as keyof typeof ALLOWLIST_FILTERS
285+
];
286+
if (allowlistFilter) {
287+
filtered = filtered.filter(allowlistFilter);
288+
}
289+
}
290+
291+
return filtered;
292+
}, [finalityProviders, filter, selectedBsn]);
234293

235294
const state = useMemo(
236295
() => ({
@@ -242,6 +301,7 @@ export function FinalityProviderBsnState({ children }: PropsWithChildren) {
242301
hasNextPage,
243302
fetchNextPage,
244303
selectedBsnId,
304+
selectedBsn,
245305
setSelectedBsnId,
246306
selectedProviderIds,
247307
setSelectedProviderIds,
@@ -262,6 +322,7 @@ export function FinalityProviderBsnState({ children }: PropsWithChildren) {
262322
hasNextPage,
263323
fetchNextPage,
264324
selectedBsnId,
325+
selectedBsn,
265326
setSelectedBsnId,
266327
selectedProviderIds,
267328
setSelectedProviderIds,

src/ui/common/types/bsn.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ export interface Bsn {
44
description: string;
55
logoUrl: string;
66
activeTvl?: number;
7+
type: string; // "COSMOS" | "ROLLUP"
8+
allowlist?: string[]; // BTC FP pubkeys (hex)
79
}

0 commit comments

Comments
 (0)