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

Commit 87b1c66

Browse files
fix: resolve comments
1 parent 1ecde55 commit 87b1c66

3 files changed

Lines changed: 218 additions & 129 deletions

File tree

src/ui/common/services/bsnService.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,20 @@ export const BSN_CONFIGS: Record<string, BsnConfig> = {
3232
COSMOS: {
3333
modalTitle: "Select Cosmos Finality Provider",
3434
filterOptions: [
35-
{ value: "active", label: "Active" },
36-
{ value: "inactive", label: "Inactive" },
35+
{ value: "registered", label: "Registered" },
36+
{ value: "slashed", label: "Slashed" },
3737
],
3838
fpFilterBehavior: "status-based",
3939
},
4040
// Roll-up BSN config
4141
ROLLUP: {
4242
modalTitle: "Select Roll Up Finality Provider",
4343
filterOptions: [
44-
{ value: "active", label: "Allowlisted" },
45-
{ value: "inactive", label: "Not Allowlisted" },
44+
{ value: "allowlisted", label: "Allowlisted" },
45+
{ value: "not-allowlisted", label: "Not Allowlisted" },
46+
{ value: "slashed", label: "Slashed" },
4647
],
47-
fpFilterBehavior: "allowlist-based",
48+
fpFilterBehavior: "status-based",
4849
},
4950
};
5051

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import { getNetworkConfigBBN } from "@/ui/common/config/network/bbn";
2+
import { Bsn } from "@/ui/common/types/bsn";
3+
import {
4+
FinalityProviderState as FinalityProviderStateEnum,
5+
type FinalityProvider,
6+
} from "@/ui/common/types/finalityProviders";
7+
8+
const { chainId: BBN_CHAIN_ID } = getNetworkConfigBBN();
9+
10+
/**
11+
* Normalizes hex string by removing 0x prefix and converting to lowercase
12+
*/
13+
export const normalizeHex = (hex?: string): string =>
14+
(hex ?? "").trim().toLowerCase().replace(/^0x/, "");
15+
16+
// BSN-specific status filters to handle different BSN behaviors
17+
const BABYLON_STATUS_FILTERS = {
18+
active: (fp: FinalityProvider) =>
19+
fp.state === FinalityProviderStateEnum.ACTIVE,
20+
inactive: (fp: FinalityProvider) =>
21+
fp.state === FinalityProviderStateEnum.INACTIVE,
22+
jailed: (fp: FinalityProvider) =>
23+
fp.state === FinalityProviderStateEnum.JAILED,
24+
slashed: (fp: FinalityProvider) =>
25+
fp.state === FinalityProviderStateEnum.SLASHED,
26+
};
27+
28+
const COSMOS_STATUS_FILTERS = {
29+
registered: (fp: FinalityProvider) =>
30+
fp.state === FinalityProviderStateEnum.INACTIVE,
31+
slashed: (fp: FinalityProvider) =>
32+
fp.state === FinalityProviderStateEnum.SLASHED,
33+
};
34+
35+
const BSN_STATUS_FILTERS = {
36+
[BBN_CHAIN_ID]: BABYLON_STATUS_FILTERS,
37+
COSMOS: COSMOS_STATUS_FILTERS,
38+
};
39+
40+
/**
41+
* Creates dynamic status filters for ROLLUP BSNs that combine allowlist status with FP state
42+
*/
43+
export const createRollupStatusFilters = (selectedBsn: Bsn | undefined) => {
44+
const allowSet = new Set((selectedBsn?.allowlist || []).map(normalizeHex));
45+
46+
return {
47+
allowlisted: (fp: FinalityProvider) => {
48+
const isAllowlisted = allowSet.has(normalizeHex(fp.btcPk));
49+
return isAllowlisted && fp.state !== FinalityProviderStateEnum.SLASHED;
50+
},
51+
"not-allowlisted": (fp: FinalityProvider) => {
52+
const isAllowlisted = allowSet.has(normalizeHex(fp.btcPk));
53+
return !isAllowlisted && fp.state !== FinalityProviderStateEnum.SLASHED;
54+
},
55+
slashed: (fp: FinalityProvider) =>
56+
fp.state === FinalityProviderStateEnum.SLASHED,
57+
};
58+
};
59+
60+
/**
61+
* Creates allowlist filters for BSN-based filtering
62+
*/
63+
export const createAllowlistFilters = (selectedBsn: Bsn | undefined) => {
64+
const allowSet = new Set((selectedBsn?.allowlist || []).map(normalizeHex));
65+
66+
return {
67+
allowlisted: (fp: FinalityProvider) => allowSet.has(normalizeHex(fp.btcPk)),
68+
"not-allowlisted": (fp: FinalityProvider) =>
69+
!allowSet.has(normalizeHex(fp.btcPk)),
70+
};
71+
};
72+
73+
/**
74+
* Applies ROLLUP-specific status filtering that combines allowlist status with FP state
75+
*/
76+
export const applyRollupStatusFilter = (
77+
providers: FinalityProvider[],
78+
filterValue: string,
79+
selectedBsn: Bsn | undefined,
80+
): FinalityProvider[] => {
81+
const rollupStatusFilters = createRollupStatusFilters(selectedBsn);
82+
const statusFilter =
83+
rollupStatusFilters[filterValue as keyof typeof rollupStatusFilters];
84+
return statusFilter ? providers.filter(statusFilter) : providers;
85+
};
86+
87+
/**
88+
* Applies standard status filtering for BABYLON and COSMOS BSNs
89+
*/
90+
export const applyStandardStatusFilter = (
91+
providers: FinalityProvider[],
92+
filterValue: string,
93+
bsnKey: string,
94+
): FinalityProvider[] => {
95+
const bsnStatusFilters =
96+
BSN_STATUS_FILTERS[bsnKey] || BSN_STATUS_FILTERS[BBN_CHAIN_ID];
97+
const statusFilter =
98+
bsnStatusFilters[filterValue as keyof typeof bsnStatusFilters];
99+
return statusFilter ? providers.filter(statusFilter) : providers;
100+
};
101+
102+
export interface FinalityProviderFilterState {
103+
searchTerm: string;
104+
providerStatus:
105+
| "active"
106+
| "inactive"
107+
| "registered"
108+
| "allowlisted"
109+
| "not-allowlisted"
110+
| "slashed"
111+
| "";
112+
allowlistStatus: "allowlisted" | "not-allowlisted" | "";
113+
}
114+
115+
/**
116+
* Main filtering function that applies BSN-aware filtering logic
117+
* Handles both allowlist-based and status-based filtering behaviors
118+
*/
119+
export const filterFinalityProvidersByBsn = (
120+
providers: FinalityProvider[],
121+
filter: FinalityProviderFilterState,
122+
selectedBsn: Bsn | undefined,
123+
fpFilterBehavior: "status-based" | "allowlist-based",
124+
): FinalityProvider[] => {
125+
let filtered = providers;
126+
127+
// Apply BSN-aware filtering based on provider status
128+
if (filter.providerStatus) {
129+
if (fpFilterBehavior === "allowlist-based") {
130+
// For rollup BSNs: filter all FPs by allowlist (regardless of active/inactive state)
131+
const allowSet = new Set(
132+
(selectedBsn?.allowlist || []).map(normalizeHex),
133+
);
134+
135+
filtered = filtered.filter((fp) => {
136+
const isAllowlisted = allowSet.has(normalizeHex(fp.btcPk));
137+
138+
if (filter.providerStatus === "active") {
139+
return isAllowlisted;
140+
} else if (filter.providerStatus === "inactive") {
141+
return !isAllowlisted;
142+
} else {
143+
// Handle slashed state for rollups
144+
return fp.state === FinalityProviderStateEnum.SLASHED;
145+
}
146+
});
147+
} else if (fpFilterBehavior === "status-based") {
148+
// For status-based BSNs: filter by finality provider state using BSN-specific filters
149+
const bsnKey =
150+
selectedBsn?.id === BBN_CHAIN_ID
151+
? BBN_CHAIN_ID
152+
: selectedBsn?.type || BBN_CHAIN_ID;
153+
154+
if (bsnKey === "ROLLUP") {
155+
filtered = applyRollupStatusFilter(
156+
filtered,
157+
filter.providerStatus,
158+
selectedBsn,
159+
);
160+
} else {
161+
filtered = applyStandardStatusFilter(
162+
filtered,
163+
filter.providerStatus,
164+
bsnKey,
165+
);
166+
}
167+
}
168+
}
169+
170+
if (filter.allowlistStatus) {
171+
const allowlistFilters = createAllowlistFilters(selectedBsn);
172+
const allowlistFilter =
173+
allowlistFilters[filter.allowlistStatus as keyof typeof allowlistFilters];
174+
if (allowlistFilter) {
175+
filtered = filtered.filter(allowlistFilter);
176+
}
177+
}
178+
179+
return filtered;
180+
};
181+
182+
/**
183+
* Determines if a finality provider row should be selectable based on BSN rules
184+
*/
185+
export const isFinalityProviderRowSelectable = (
186+
row: FinalityProvider,
187+
selectedBsnId: string | undefined,
188+
selectedBsn: Bsn | undefined,
189+
): boolean => {
190+
const statusAllowed =
191+
row.state === FinalityProviderStateEnum.ACTIVE ||
192+
row.state === FinalityProviderStateEnum.INACTIVE;
193+
194+
// For selection (not filtering), only restrict based on allowlist for non-Babylon BSNs
195+
const allowlistAllowed =
196+
!selectedBsnId ||
197+
selectedBsnId === BBN_CHAIN_ID ||
198+
(selectedBsn?.allowlist || []).some(
199+
(allowedPk) => normalizeHex(allowedPk) === normalizeHex(row.btcPk),
200+
);
201+
202+
return statusAllowed && allowlistAllowed;
203+
};

src/ui/common/state/FinalityProviderBsnState.tsx

Lines changed: 9 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import {
1010
getBsnConfig,
1111
type BsnFilterOption,
1212
} from "@/ui/common/services/bsnService";
13+
import {
14+
filterFinalityProvidersByBsn,
15+
isFinalityProviderRowSelectable,
16+
type FinalityProviderFilterState,
17+
} from "@/ui/common/services/finalityProviderService";
1318
import { Bsn } from "@/ui/common/types/bsn";
1419
import {
1520
FinalityProviderState as FinalityProviderStateEnum,
@@ -19,19 +24,12 @@ import { createStateUtils } from "@/ui/common/utils/createStateUtils";
1924

2025
import { getBsnLogoUrl } from "../utils/bsnLogo";
2126

22-
const normalizeHex = (hex?: string): string =>
23-
(hex ?? "").trim().toLowerCase().replace(/^0x/, "");
24-
2527
interface SortState {
2628
field?: string;
2729
direction?: "asc" | "desc";
2830
}
2931

30-
interface FilterState {
31-
searchTerm: string;
32-
providerStatus: "active" | "inactive" | "";
33-
allowlistStatus: "allowlisted" | "not-allowlisted" | "";
34-
}
32+
type FilterState = FinalityProviderFilterState;
3533

3634
const { chainId: BBN_CHAIN_ID } = getNetworkConfigBBN();
3735

@@ -90,107 +88,7 @@ const SORT_DIRECTIONS = {
9088
asc: undefined,
9189
} as const;
9290

93-
// BSN-specific status filters to handle different BSN behaviors
94-
const BABYLON_STATUS_FILTERS = {
95-
active: (fp: FinalityProvider) =>
96-
fp.state === FinalityProviderStateEnum.ACTIVE,
97-
inactive: (fp: FinalityProvider) =>
98-
fp.state === FinalityProviderStateEnum.INACTIVE,
99-
jailed: (fp: FinalityProvider) =>
100-
fp.state === FinalityProviderStateEnum.JAILED,
101-
slashed: (fp: FinalityProvider) =>
102-
fp.state === FinalityProviderStateEnum.SLASHED,
103-
};
104-
105-
const COSMOS_STATUS_FILTERS = {
106-
active: (fp: FinalityProvider) =>
107-
fp.state === FinalityProviderStateEnum.ACTIVE,
108-
inactive: (fp: FinalityProvider) =>
109-
fp.state === FinalityProviderStateEnum.INACTIVE,
110-
};
111-
112-
const ROLLUP_STATUS_FILTERS = {
113-
active: (fp: FinalityProvider) =>
114-
fp.state === FinalityProviderStateEnum.ACTIVE,
115-
inactive: (fp: FinalityProvider) =>
116-
fp.state === FinalityProviderStateEnum.INACTIVE,
117-
slashed: (fp: FinalityProvider) =>
118-
fp.state === FinalityProviderStateEnum.SLASHED,
119-
};
120-
121-
const BSN_STATUS_FILTERS = {
122-
[BBN_CHAIN_ID]: BABYLON_STATUS_FILTERS,
123-
COSMOS: COSMOS_STATUS_FILTERS,
124-
ROLLUP: ROLLUP_STATUS_FILTERS,
125-
};
126-
127-
const createAllowlistFilters = (selectedBsn: Bsn | undefined) => {
128-
const allowSet = new Set((selectedBsn?.allowlist || []).map(normalizeHex));
129-
130-
return {
131-
allowlisted: (fp: FinalityProvider) => allowSet.has(normalizeHex(fp.btcPk)),
132-
"not-allowlisted": (fp: FinalityProvider) =>
133-
!allowSet.has(normalizeHex(fp.btcPk)),
134-
};
135-
};
136-
137-
const filterFinalityProvidersByBsn = (
138-
providers: FinalityProvider[],
139-
filter: FilterState,
140-
selectedBsn: Bsn | undefined,
141-
fpFilterBehavior: "status-based" | "allowlist-based",
142-
): FinalityProvider[] => {
143-
let filtered = providers;
144-
145-
// Apply BSN-aware filtering based on provider status
146-
if (filter.providerStatus) {
147-
if (fpFilterBehavior === "allowlist-based") {
148-
// For rollup BSNs: filter all FPs by allowlist (regardless of active/inactive state)
149-
const allowSet = new Set(
150-
(selectedBsn?.allowlist || []).map(normalizeHex),
151-
);
152-
153-
filtered = filtered.filter((fp) => {
154-
const isAllowlisted = allowSet.has(normalizeHex(fp.btcPk));
155-
156-
if (filter.providerStatus === "active") {
157-
return isAllowlisted;
158-
} else if (filter.providerStatus === "inactive") {
159-
return !isAllowlisted;
160-
} else {
161-
// Handle slashed state for rollups
162-
return fp.state === FinalityProviderStateEnum.SLASHED;
163-
}
164-
});
165-
} else if (fpFilterBehavior === "status-based") {
166-
// For status-based BSNs: filter by finality provider state using BSN-specific filters
167-
const bsnKey =
168-
selectedBsn?.id === BBN_CHAIN_ID
169-
? BBN_CHAIN_ID
170-
: selectedBsn?.type || BBN_CHAIN_ID;
171-
const bsnStatusFilters =
172-
BSN_STATUS_FILTERS[bsnKey] || BSN_STATUS_FILTERS[BBN_CHAIN_ID];
173-
const statusFilter =
174-
bsnStatusFilters[
175-
filter.providerStatus as keyof typeof bsnStatusFilters
176-
];
177-
if (statusFilter) {
178-
filtered = filtered.filter(statusFilter);
179-
}
180-
}
181-
}
182-
183-
if (filter.allowlistStatus) {
184-
const allowlistFilters = createAllowlistFilters(selectedBsn);
185-
const allowlistFilter =
186-
allowlistFilters[filter.allowlistStatus as keyof typeof allowlistFilters];
187-
if (allowlistFilter) {
188-
filtered = filtered.filter(allowlistFilter);
189-
}
190-
}
191-
192-
return filtered;
193-
};
91+
// Component-specific constants remain in the state file
19492

19593
const FILTERS = {
19694
searchTerm: (fp: FinalityProvider, filter: FilterState) => {
@@ -339,21 +237,8 @@ export function FinalityProviderBsnState({ children }: PropsWithChildren) {
339237
}, []);
340238

341239
const isRowSelectable = useCallback(
342-
(row: FinalityProvider) => {
343-
const statusAllowed =
344-
row.state === FinalityProviderStateEnum.ACTIVE ||
345-
row.state === FinalityProviderStateEnum.INACTIVE;
346-
347-
// For selection (not filtering), only restrict based on allowlist for non-Babylon BSNs
348-
const allowlistAllowed =
349-
!selectedBsnId ||
350-
selectedBsnId === BBN_CHAIN_ID ||
351-
(selectedBsn?.allowlist || []).some(
352-
(allowedPk) => normalizeHex(allowedPk) === normalizeHex(row.btcPk),
353-
);
354-
355-
return statusAllowed && allowlistAllowed;
356-
},
240+
(row: FinalityProvider) =>
241+
isFinalityProviderRowSelectable(row, selectedBsnId, selectedBsn),
357242
[selectedBsnId, selectedBsn],
358243
);
359244

0 commit comments

Comments
 (0)