diff --git a/apps/commune-validator/src/app/(pages)/modules/page.tsx b/apps/commune-validator/src/app/(pages)/modules/page.tsx
index 631a834..3f8b6e4 100644
--- a/apps/commune-validator/src/app/(pages)/modules/page.tsx
+++ b/apps/commune-validator/src/app/(pages)/modules/page.tsx
@@ -1,11 +1,12 @@
import { Suspense } from "react";
+import type { Module } from "~/utils/types";
import { ModuleCard } from "~/app/components/module-card";
import { PaginationControls } from "~/app/components/pagination-controls";
import { ViewControls } from "~/app/components/view-controls";
import { api } from "~/trpc/server";
-export default async function Page({
+export default async function ModulesPage({
searchParams,
}: {
searchParams: { page?: string; sortBy?: string; order?: string };
@@ -17,7 +18,6 @@ export default async function Page({
const { modules, metadata } = await api.module.paginatedAll({
page: currentPage,
limit: 24,
- // @ts-expect-error - TS doesn't know about sortBy for some reason
sortBy: sortBy,
order: order,
});
@@ -29,7 +29,7 @@ export default async function Page({
{modules.length ? (
- modules.map((module) => (
+ modules.map((module: Module) => (
- {data.map((subnet) => (
-
- ))}
-
+ <>
+ Loading view controls...}>
+
+
+
+ {subnets.length ? (
+ subnets.map((subnet: Subnet) => (
+
+ ))
+ ) : (
+
No subnets found
+ )}
+
+ Loading...}>
+
+
+ >
);
}
diff --git a/apps/commune-validator/src/app/components/subnet-view-controls.tsx b/apps/commune-validator/src/app/components/subnet-view-controls.tsx
new file mode 100644
index 0000000..eb3a232
--- /dev/null
+++ b/apps/commune-validator/src/app/components/subnet-view-controls.tsx
@@ -0,0 +1,70 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+
+type SortField =
+ | "id"
+ | "founderShare"
+ | "incentiveRatio"
+ | "proposalRewardTreasuryAllocation"
+ | "minValidatorStake"
+ | "createdAt";
+
+type SortOrder = "asc" | "desc";
+
+const sortFieldLabels: Record = {
+ id: "ID",
+ founderShare: "Founder Share",
+ incentiveRatio: "Incentive Ratio",
+ proposalRewardTreasuryAllocation: "Proposal Reward Alloc.",
+ minValidatorStake: "Min. Vali. Stake",
+ createdAt: "Creation Date",
+};
+
+export function SubnetViewControls() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const [sortField, setSortField] = useState(
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ (searchParams.get("sortBy") as SortField) ?? "id",
+ );
+ const [sortOrder, setSortOrder] = useState(
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ (searchParams.get("order") as SortOrder) ?? "asc",
+ );
+
+ useEffect(() => {
+ const newSearchParams = new URLSearchParams(searchParams);
+ newSearchParams.set("sortBy", sortField);
+ newSearchParams.set("order", sortOrder);
+ router.push(`?${newSearchParams.toString()}`, { scroll: false });
+ }, [sortField, sortOrder, router, searchParams]);
+
+ const handleSortChange = (field: SortField) => {
+ const newOrder =
+ field === sortField && sortOrder === "asc" ? "desc" : "asc";
+ setSortField(field);
+ setSortOrder(newOrder);
+ };
+
+ return (
+
+ Sort by:
+ {(Object.keys(sortFieldLabels) as SortField[]).map((field) => (
+
+ ))}
+
+ );
+}
diff --git a/packages/api/src/router/subnet.ts b/packages/api/src/router/subnet.ts
index ba7845c..1015430 100644
--- a/packages/api/src/router/subnet.ts
+++ b/packages/api/src/router/subnet.ts
@@ -23,6 +23,51 @@ export const subnetRouter = {
where: eq(subnetDataSchema.id, input.id),
});
}),
+ paginatedAll: publicProcedure
+ .input(
+ z.object({
+ page: z.number().int().positive().default(1),
+ limit: z.number().int().positive().max(100).default(50),
+ sortBy: z
+ .enum([
+ "id",
+ "founderShare",
+ "incentiveRatio",
+ "proposalRewardTreasuryAllocation",
+ "minValidatorStake",
+ "createdAt",
+ ])
+ .default("id"),
+ order: z.enum(["asc", "desc"]).default("asc"),
+ }),
+ )
+ .query(async ({ ctx, input }) => {
+ const { page, limit, sortBy, order } = input;
+ const offset = (page - 1) * limit;
+
+ const subnets = await ctx.db.query.subnetDataSchema.findMany({
+ limit: limit,
+ offset: offset,
+ orderBy: (subnetData, { asc, desc }) => [
+ order === "asc" ? asc(subnetData[sortBy]) : desc(subnetData[sortBy]),
+ ],
+ });
+
+ const totalCount = await ctx.db
+ .select({ count: sql`count(*)` })
+ .from(subnetDataSchema)
+ .then((result) => Number(result[0]?.count));
+
+ return {
+ subnets,
+ metadata: {
+ currentPage: page,
+ pageSize: limit,
+ totalCount,
+ totalPages: Math.ceil(totalCount / limit),
+ },
+ };
+ }),
byUserSubnetData: publicProcedure
.input(z.object({ userKey: z.string() }))
.query(async ({ ctx, input }) => {
diff --git a/packages/subspace/queries/index.ts b/packages/subspace/queries/index.ts
index 0ea6182..b740342 100644
--- a/packages/subspace/queries/index.ts
+++ b/packages/subspace/queries/index.ts
@@ -21,12 +21,12 @@ import type {
} from "@commune-ts/utils";
import {
checkSS58,
- STAKE_OUT_DATA_SCHEMA,
GOVERNANCE_CONFIG_SCHEMA,
isSS58,
- STAKE_FROM_SCHEMA,
MODULE_BURN_CONFIG_SCHEMA,
NetworkSubnetConfigSchema,
+ STAKE_FROM_SCHEMA,
+ STAKE_OUT_DATA_SCHEMA,
SUBSPACE_MODULE_SCHEMA,
} from "@commune-ts/types";
import {
@@ -36,7 +36,6 @@ import {
standardizeUidToSS58address,
} from "@commune-ts/utils";
-
export { ApiPromise };
// == chain ==
@@ -348,7 +347,7 @@ export async function queryStakeOutCORRECT(
if (!response.ok) {
throw new Error("Failed to fetch data");
}
- const stakeOutData = STAKE_OUT_DATA_SCHEMA.parse(await response.json())
+ const stakeOutData = STAKE_OUT_DATA_SCHEMA.parse(await response.json());
return stakeOutData;
}
@@ -521,8 +520,9 @@ export async function queryUserTotalStaked(
* @param netuidWhitelist if empty, modules from all subnets are returned
*/
-
-export async function querySubnetParams(api: Api): Promise {
+export async function querySubnetParams(
+ api: Api,
+): Promise {
const subnetProps: SubspaceStorageName[] = [
"subnetNames",
"immunityPeriod",
@@ -550,7 +550,6 @@ export async function querySubnetParams(api: Api): Promise>
+ stakeFromStorage: Map>,
) {
const stakerMap = stakeFromStorage.get(targetKey);
let totalStake = 0n;
@@ -618,13 +622,17 @@ export async function queryRegisteredModulesInfo(
"dividends",
"delegationFee",
"stakeFrom",
- ]
+ ];
- const extraPropsQuery: { subspaceModule: SubspaceStorageName[] } = { subspaceModule: moduleProps }
+ const extraPropsQuery: { subspaceModule: SubspaceStorageName[] } = {
+ subspaceModule: moduleProps,
+ };
const modulesInfo = await queryChain(api, extraPropsQuery, netuid);
const processedModules = standardizeUidToSS58address(modulesInfo, uidToSS58);
const moduleMap: SubspaceModule[] = [];
- const parsedStakeFromStorage = STAKE_FROM_SCHEMA.parse({ stakeFromStorage: processedModules.stakeFrom });
+ const parsedStakeFromStorage = STAKE_FROM_SCHEMA.parse({
+ stakeFromStorage: processedModules.stakeFrom,
+ });
for (const uid of Object.keys(uidToSS58)) {
const moduleKey = uidToSS58[uid];
@@ -646,10 +654,13 @@ export async function queryRegisteredModulesInfo(
incentive: processedModules.incentive[moduleKey],
dividends: processedModules.dividends[moduleKey],
delegationFee: processedModules.delegationFee[moduleKey],
- totalStaked: keyStakeFrom(moduleKey, parsedStakeFromStorage.stakeFromStorage),
- totalStakers: parsedStakeFromStorage.stakeFromStorage.get(moduleKey)?.size ?? 0,
-
- })
+ totalStaked: keyStakeFrom(
+ moduleKey,
+ parsedStakeFromStorage.stakeFromStorage,
+ ),
+ totalStakers:
+ parsedStakeFromStorage.stakeFromStorage.get(moduleKey)?.size ?? 0,
+ });
moduleMap.push(module);
}
return moduleMap;