diff --git a/apps/commune-validator/src/app/(expanded-pages)/module/[...slug]/page.tsx b/apps/commune-validator/src/app/(expanded-pages)/module/[...slug]/page.tsx new file mode 100644 index 00000000..1f62f54d --- /dev/null +++ b/apps/commune-validator/src/app/(expanded-pages)/module/[...slug]/page.tsx @@ -0,0 +1,159 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { ArrowLeftIcon } from "@heroicons/react/16/solid"; + +import { MarkdownView } from "@commune-ts/ui/markdown-view"; +import { + fetchCustomMetadata, + formatToken, + smallAddress, +} from "@commune-ts/utils"; + +import type { Module } from "~/utils/types"; +import { ReportModule } from "~/app/components/report-module"; +import { api } from "~/trpc/server"; + +interface Params { + params: { + slug: string[]; + }; +} + +interface CustomMetadata { + Ok?: { + title?: string; + body?: string; + }; +} + +export default async function ModulePage({ params }: Params) { + const { slug } = params; + + if (slug.length !== 1) { + notFound(); + } + + const id = slug[0]; + + if (!/^\d+$/.test(String(id))) { + notFound(); + } + + const mdl = await api.module.byId({ id: Number(id) }); + + if (!mdl) { + notFound(); + } + + const metadata = (await fetchCustomMetadata( + "proposal", + mdl.id, + mdl.metadataUri ?? "", + )) as CustomMetadata; + + const title = metadata.Ok?.title ?? "No Metadata"; + // limited to 140 characters + const description = metadata.Ok?.body ?? "This module has no custom metadata"; + + return ( +
+
+ + + Go back to modules list + +

+ {title} +

+
+ +
+
+
+
+
+

Description

+ +
+
+
+ +
+
+
+ ); +} + +function ModuleDataGrid({ module }: { module: Module }) { + const dataGroups = [ + { + title: "General Information", + fields: [ + { label: "Module ID", value: module.moduleId }, + { label: "Netuid", value: module.netuid }, + { label: "Name", value: module.name ?? "N/A" }, + { label: "Module Key", value: smallAddress(module.moduleKey) }, + { label: "At Block", value: module.atBlock }, + { + label: "Registration Block", + value: module.registrationBlock ?? "N/A", + }, + { + label: "Registered At", + value: new Date(module.createdAt).toLocaleString(), + }, + ], + }, + { + title: "URIs", + fields: [ + { label: "Address URI", value: module.addressUri ?? "N/A" }, + { label: "Metadata URI", value: module.metadataUri ?? "N/A" }, + ], + }, + { + title: "Economic Parameters", + fields: [ + { label: "Emission", value: formatToken(module.totalStakers ?? 0) }, + { label: "Incentive", value: formatToken(module.incentive ?? 0) }, + { label: "Dividend", value: formatToken(module.dividend ?? 0) }, + { label: "Delegation Fee", value: `${module.delegationFee ?? 0}%` }, + ], + }, + { + title: "Staking Information", + fields: [ + { label: "Total Staked", value: formatToken(module.totalStaked ?? 0) }, + { label: "Total Stakers", value: module.totalStakers ?? 0 }, + { + label: "Total Rewards", + value: formatToken(module.totalRewards ?? 0), + }, + ], + }, + ]; + + return ( +
+ {dataGroups.map((group, index) => ( +
+

{group.title}

+
+ {group.fields.map((field, fieldIndex) => ( +
+ {field.label}: + {field.value} +
+ ))} +
+
+ ))} +
+ ); +} diff --git a/apps/commune-validator/src/app/(expanded-pages)/subnet/[...slug]/page.tsx b/apps/commune-validator/src/app/(expanded-pages)/subnet/[...slug]/page.tsx new file mode 100644 index 00000000..17700fa0 --- /dev/null +++ b/apps/commune-validator/src/app/(expanded-pages)/subnet/[...slug]/page.tsx @@ -0,0 +1,202 @@ +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { ArrowLeftIcon } from "@heroicons/react/16/solid"; + +import { MarkdownView } from "@commune-ts/ui/markdown-view"; +import { fetchCustomMetadata, smallAddress } from "@commune-ts/utils"; + +import type { Subnet } from "~/utils/types"; +// import { ReportSubnet } from "~/app/components/report-Subnet"; +import { api } from "~/trpc/server"; + +interface Params { + params: { + slug: string[]; + }; +} + +interface CustomMetadata { + Ok?: { + title?: string; + body?: string; + }; +} + +export default async function SubnetPage({ params }: Params) { + const { slug } = params; + + if (slug.length !== 1) { + notFound(); + } + + const id = slug[0]; + + if (!/^\d+$/.test(String(id))) { + notFound(); + } + + const sbnt = await api.subnet.byId({ id: Number(id) }); + + if (!sbnt) { + notFound(); + } + + const metadata = (await fetchCustomMetadata( + "proposal", + sbnt.id, + sbnt.subnetMetadata ?? "", + )) as CustomMetadata; + + const title = metadata.Ok?.title ?? "No Metadata"; + + const description = metadata.Ok?.body ?? "This Subnet has no custom metadata"; + + return ( +
+
+ + + Go back to Subnets list + +

+ {title} +

+
+
+
+
+

Description

+ +
+
+
+ +
+
+
+ ); +} + +function SubnetDataGrid({ subnet }: { subnet: Subnet }) { + const dataGroups = [ + { + title: "General Information", + fields: [ + { label: "Subnet ID", value: subnet.id }, + { label: "Netuid", value: subnet.netuid }, + { label: "Name", value: subnet.name }, + { label: "At Block", value: subnet.atBlock }, + { label: "Tempo", value: subnet.tempo }, + { label: "Founder", value: smallAddress(subnet.founder) }, + ], + }, + { + title: "Weight Configuration", + fields: [ + { label: "Min Allowed Weights", value: subnet.minAllowedWeights }, + { label: "Max Allowed Weights", value: subnet.maxAllowedWeights }, + { label: "Max Allowed UIDs", value: subnet.maxAllowedUids }, + { label: "Max Weight Age", value: subnet.maxWeightAge }, + { label: "Trust Ratio", value: subnet.trustRatio }, + { + label: "Max Set Weight Calls/Epoch", + value: subnet.maximumSetWeightCallsPerEpoch, + }, + ], + }, + { + title: "Economic Parameters", + fields: [ + { label: "Founder Share", value: subnet.founderShare }, + { label: "Incentive Ratio", value: subnet.incentiveRatio }, + { label: "Subnet Emission", value: subnet.subnetEmission.toString() }, + { label: "Bonds MA", value: subnet.bondsMa }, + { label: "Immunity Period", value: subnet.immunityPeriod }, + ], + }, + { + title: "Governance Configuration", + fields: [ + { label: "Proposal Cost", value: subnet.proposalCost.toString() }, + { label: "Proposal Expiration", value: subnet.proposalExpiration }, + { label: "Vote Mode", value: subnet.voteMode }, + { + label: "Proposal Reward Treasury Allocation", + value: subnet.proposalRewardTreasuryAllocation, + }, + { + label: "Max Proposal Reward Treasury Allocation", + value: subnet.maxProposalRewardTreasuryAllocation.toString(), + }, + { + label: "Proposal Reward Interval", + value: subnet.proposalRewardInterval, + }, + ], + }, + { + title: "Burn Configuration", + fields: [ + { label: "Min Burn", value: subnet.minBurn.toString() }, + { label: "Max Burn", value: subnet.maxBurn.toString() }, + { label: "Adjustment Alpha", value: subnet.adjustmentAlpha }, + { + label: "Target Registrations Interval", + value: subnet.targetRegistrationsInterval, + }, + { + label: "Target Registrations Per Interval", + value: subnet.targetRegistrationsPerInterval, + }, + { + label: "Max Registrations Per Interval", + value: subnet.maxRegistrationsPerInterval, + }, + ], + }, + { + title: "Additional Information", + fields: [ + { + label: "Min Validator Stake", + value: subnet.minValidatorStake?.toString(), + }, + { label: "Max Allowed Validators", value: subnet.maxAllowedValidators }, + { + label: "Created At", + value: new Date(subnet.createdAt).toLocaleString(), + }, + { + label: "Deleted At", + value: subnet.deletedAt + ? new Date(subnet.deletedAt).toLocaleString() + : "N/A", + }, + ], + }, + ]; + + return ( +
+ {dataGroups.map((group, index) => ( +
+

{group.title}

+
+ {group.fields.map((field, fieldIndex) => ( +
+ {field.label}: + {field.value ?? "N/A"} +
+ ))} +
+
+ ))} +
+ ); +} diff --git a/apps/commune-validator/src/app/tutorial/page.tsx b/apps/commune-validator/src/app/(expanded-pages)/tutorial/page.tsx similarity index 100% rename from apps/commune-validator/src/app/tutorial/page.tsx rename to apps/commune-validator/src/app/(expanded-pages)/tutorial/page.tsx diff --git a/apps/commune-validator/src/app/(pages)/page.tsx b/apps/commune-validator/src/app/(pages)/page.tsx index 4662cd37..2a35e602 100644 --- a/apps/commune-validator/src/app/(pages)/page.tsx +++ b/apps/commune-validator/src/app/(pages)/page.tsx @@ -17,6 +17,7 @@ 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, }); @@ -34,7 +35,6 @@ export default async function Page({ key={module.id} name={module.name ?? ""} moduleKey={module.moduleKey} - metadata={module.metadataUri} /> )) ) : ( diff --git a/apps/commune-validator/src/app/(pages)/subnets/page.tsx b/apps/commune-validator/src/app/(pages)/subnets/page.tsx index bd0d42df..841cd638 100644 --- a/apps/commune-validator/src/app/(pages)/subnets/page.tsx +++ b/apps/commune-validator/src/app/(pages)/subnets/page.tsx @@ -7,7 +7,12 @@ export default async function SubnetsPage() { return (
{data.map((subnet) => ( - + ))}
); diff --git a/apps/commune-validator/src/app/(pages)/weighted-modules/page.tsx b/apps/commune-validator/src/app/(pages)/weighted-modules/page.tsx index 69431aff..071561b3 100644 --- a/apps/commune-validator/src/app/(pages)/weighted-modules/page.tsx +++ b/apps/commune-validator/src/app/(pages)/weighted-modules/page.tsx @@ -29,8 +29,8 @@ export default function Page() { id={module.id} key={module.id} name={module.name} + percentage={module.percentage} moduleKey={module.address} - metadata={null} // TODO - metadataUri /> ))} diff --git a/apps/commune-validator/src/app/(pages)/weighted-subnets/page.tsx b/apps/commune-validator/src/app/(pages)/weighted-subnets/page.tsx index 3527d33c..7776b20b 100644 --- a/apps/commune-validator/src/app/(pages)/weighted-subnets/page.tsx +++ b/apps/commune-validator/src/app/(pages)/weighted-subnets/page.tsx @@ -25,7 +25,13 @@ export default function Page() { {weightedSubnets.length ? (
{weightedSubnets.map((subnet) => ( - + ))}
) : ( diff --git a/apps/commune-validator/src/app/components/delegate-module-weight.tsx b/apps/commune-validator/src/app/components/delegate-module-weight.tsx index 2ca10a98..4fa92af9 100644 --- a/apps/commune-validator/src/app/components/delegate-module-weight.tsx +++ b/apps/commune-validator/src/app/components/delegate-module-weight.tsx @@ -9,7 +9,6 @@ import { useDelegateModuleStore } from "~/stores/delegateModuleStore"; interface DelegateModuleWeightProps { id: number; - title: string; name: string; moduleKey: string; } @@ -32,9 +31,8 @@ export function DelegateModuleWeight(props: DelegateModuleWeightProps) { } else { addModule({ id: props.id, - address: props.moduleKey, - title: props.title, name: props.name, + address: props.moduleKey, }); } }; diff --git a/apps/commune-validator/src/app/components/delegate-subnet-weight.tsx b/apps/commune-validator/src/app/components/delegate-subnet-weight.tsx index 85b64b3c..3ba3cfc0 100644 --- a/apps/commune-validator/src/app/components/delegate-subnet-weight.tsx +++ b/apps/commune-validator/src/app/components/delegate-subnet-weight.tsx @@ -5,16 +5,21 @@ import { ChartPieIcon } from "@heroicons/react/24/outline"; import { useCommune } from "@commune-ts/providers/use-commune"; import { toast } from "@commune-ts/providers/use-toast"; -import type { Subnet } from "~/utils/types"; import { useDelegateSubnetStore } from "~/stores/delegateSubnetStore"; -export function DelegateSubnetWeight({ subnet }: { subnet: Subnet }) { +interface DelegateSubnetWeightProps { + id: number; + name: string; + founderAddress: string; +} + +export function DelegateSubnetWeight(props: DelegateSubnetWeightProps) { const { delegatedSubnets, addSubnet, removeSubnet } = useDelegateSubnetStore(); const { selectedAccount } = useCommune(); - const isSubnetDelegated = delegatedSubnets.some((m) => m.id === subnet.id); + const isSubnetDelegated = delegatedSubnets.some((m) => m.id === props.id); const handleDelegateClick = () => { if (!selectedAccount?.address) { @@ -22,9 +27,13 @@ export function DelegateSubnetWeight({ subnet }: { subnet: Subnet }) { return; } if (isSubnetDelegated) { - removeSubnet(subnet.id); + removeSubnet(props.id); } else { - addSubnet({ ...subnet }); + addSubnet({ + id: props.id, + name: props.name, + founderAddress: props.founderAddress, + }); } }; diff --git a/apps/commune-validator/src/app/components/delegated-list.tsx b/apps/commune-validator/src/app/components/delegated-list.tsx index 3c4d795f..03ae258e 100644 --- a/apps/commune-validator/src/app/components/delegated-list.tsx +++ b/apps/commune-validator/src/app/components/delegated-list.tsx @@ -141,8 +141,10 @@ export function DelegatedList() { } if (userSubnetData) { const formattedSubnets = userSubnetData.map((subnet) => ({ - ...subnet.subnet_data, + id: subnet.subnet_data.id, + name: subnet.subnet_data.name, percentage: subnet.user_subnet_data.weight, + founderAddress: subnet.subnet_data.founder, })); setDelegatedSubnetsFromDB(formattedSubnets); } @@ -273,11 +275,15 @@ export function DelegatedList() { { enabled: !!selectedAccount.address }, ); await refetchSubnets(); - const formattedSubnets = updatedSubnetData?.map((subnet) => ({ - ...subnet.subnet_data, - percentage: subnet.user_subnet_data.weight, - })); - setDelegatedSubnetsFromDB(formattedSubnets ?? []); + if (updatedSubnetData) { + const formattedSubnets = updatedSubnetData.map((subnet) => ({ + id: subnet.subnet_data.id, + name: subnet.subnet_data.name, + percentage: subnet.user_subnet_data.weight, + founderAddress: subnet.subnet_data.founder, + })); + setDelegatedSubnetsFromDB(formattedSubnets); + } } setIsSubmitting(false); @@ -432,7 +438,6 @@ export function DelegatedList() { {activeTab === "modules" ? "Module" : "Subnet"} - Name {activeTab === "modules" ? "Address" : "Founder"} @@ -445,9 +450,6 @@ export function DelegatedList() { delegatedModules.map((module) => ( - {module.title} - - {module.name} @@ -493,11 +495,8 @@ export function DelegatedList() { {subnet.name} - - {subnet.name} - - {smallAddress(subnet.founder)} + {smallAddress(subnet.founderAddress)}
diff --git a/apps/commune-validator/src/app/components/module-card.tsx b/apps/commune-validator/src/app/components/module-card.tsx index 8847639e..06bd3045 100644 --- a/apps/commune-validator/src/app/components/module-card.tsx +++ b/apps/commune-validator/src/app/components/module-card.tsx @@ -1,10 +1,10 @@ "use client"; -import { useEffect, useState } from "react"; import Link from "next/link"; import { ArrowRightIcon, Squares2X2Icon } from "@heroicons/react/24/outline"; -import { fetchCustomMetadata, smallAddress } from "@commune-ts/utils"; +import { Label } from "@commune-ts/ui"; +import { smallAddress } from "@commune-ts/utils"; import { useDelegateModuleStore } from "~/stores/delegateModuleStore"; import { CopySquareButton } from "./copy-square-button"; @@ -12,61 +12,32 @@ import { DelegateModuleWeight } from "./delegate-module-weight"; interface ModuleCardProps { id: number; - name?: string; + name: string; + percentage?: number; moduleKey: string; // SS58.1 - metadata: string | null; // IPFS -} - -interface CustomMetadata { - Ok?: { - title?: string; - body?: string; - }; -} - -// Custom hook for fetching metadata -function useCustomMetadata(id: number, metadata: string | null) { - const [customMetadata, setCustomMetadata] = useState( - null, - ); - - useEffect(() => { - async function fetchMetadata() { - try { - const result = await fetchCustomMetadata( - "proposal", - id, - metadata ?? "", - ); - setCustomMetadata(result as CustomMetadata); - } catch (error) { - console.error("Error fetching custom metadata:", error); - } - } - - void fetchMetadata(); - }, [id, metadata]); - - return customMetadata; } export function ModuleCard(props: ModuleCardProps) { - const customMetadata = useCustomMetadata(props.id, props.metadata); const { delegatedModules } = useDelegateModuleStore(); const isModuleDelegated = delegatedModules.some((m) => m.id === props.id); - const title = customMetadata?.Ok?.title ?? "No Metadata"; - return (
-

- {title} -

-

{props.name ?? ""}

+
+

+ {props.name} +

+ {props.percentage && ( + + )} +
+
{" "} @@ -77,8 +48,7 @@ export function ModuleCard(props: ModuleCardProps) {
setIsOpen(!isOpen); const { delegatedSubnets } = useDelegateSubnetStore(); - const isSubnetDelegated = delegatedSubnets.some( - (s) => s.id === subnet.netuid, - ); + const isSubnetDelegated = delegatedSubnets.some((s) => s.id === id); return (
- - - + + + + {percentage && }
- +
- - {isOpen && ( -
-
- {subnetDataList.map((field) => ( - - ))} -
-
- )} -
- ); -} - -function InfoItem({ label, value }: { label: string; value: unknown }) { - return ( -
- {label}: - - {value !== null && value !== undefined ? String(value) : "N/A"} -
); } diff --git a/apps/commune-validator/src/app/module/[...slug]/page.tsx b/apps/commune-validator/src/app/module/[...slug]/page.tsx deleted file mode 100644 index cbf0d5a7..00000000 --- a/apps/commune-validator/src/app/module/[...slug]/page.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import Link from "next/link"; -import { notFound } from "next/navigation"; -import { ArrowLeftIcon } from "@heroicons/react/16/solid"; - -import { MarkdownView } from "@commune-ts/ui/markdown-view"; -import { fetchCustomMetadata, smallAddress } from "@commune-ts/utils"; - -import { ReportModule } from "~/app/components/report-module"; -import { api } from "~/trpc/server"; - -interface Params { - params: { - slug: string[]; - }; -} - -interface CustomMetadata { - Ok?: { - title?: string; - body?: string; - }; -} - -export default async function ModulePage({ params }: Params) { - const { slug } = params; - - if (slug.length !== 1) { - notFound(); - } - - const id = slug[0]; - - if (!/^\d+$/.test(String(id))) { - notFound(); - } - - const mdl = await api.module.byId({ id: Number(id) }); - - if (!mdl) { - notFound(); - } - - const metadata = (await fetchCustomMetadata( - "proposal", - mdl.id, - mdl.metadataUri ?? "", - )) as CustomMetadata; - - const title = metadata.Ok?.title ?? "No Metadata"; - // limited to 140 characters - const description = metadata.Ok?.body ?? "This module has no custom metadata"; - - return ( -
-
- - - Go back to modules list - -

- {title} -

-
- -
-
-
-
-
-

Description

- -
-
-
-
-
-
-

Publication Date

- {new Date(mdl.createdAt).toLocaleString()} -
-
-

Address

- {smallAddress(String(mdl.moduleKey))} -
-
-

Emission

- {mdl.emission ?? 0} -
-
-

Total Stakers

- {mdl.totalStakers ?? 0} -
-
-
-
-

Category

- - {mdl.incentive === mdl.dividend || - !mdl.incentive || - !mdl.dividend - ? "Inactive" - : mdl.incentive > mdl.dividend - ? "Miner" - : "Validator"} - -
-
-

Reading Time

- {calculateReadingTime(description)} -
-
-

Incentive

- {mdl.incentive ?? 0} -
-
-

Total Staked

- {mdl.totalStaked ?? 0} -
-
-
-
-
-
- ); -} - -function calculateReadingTime(text: string): string { - const wordsPerMinute = 200; // Average reading speed - const words = text.split(/\s+/).length; // Split text by whitespace and count words - const minutes = words / wordsPerMinute; - - const minutesRounded = Math.ceil(minutes); // Round up to the nearest minute - - return `${minutesRounded} min read`; -} diff --git a/apps/commune-validator/src/stores/delegateModuleStore.ts b/apps/commune-validator/src/stores/delegateModuleStore.ts index 1b1bb680..1506cdfd 100644 --- a/apps/commune-validator/src/stores/delegateModuleStore.ts +++ b/apps/commune-validator/src/stores/delegateModuleStore.ts @@ -3,9 +3,8 @@ import { persist } from "zustand/middleware"; interface DelegatedModule { id: number; - address: string; - title: string; name: string; + address: string; percentage: number; } diff --git a/apps/commune-validator/src/stores/delegateSubnetStore.ts b/apps/commune-validator/src/stores/delegateSubnetStore.ts index 26529d9f..6a5bc804 100644 --- a/apps/commune-validator/src/stores/delegateSubnetStore.ts +++ b/apps/commune-validator/src/stores/delegateSubnetStore.ts @@ -1,10 +1,11 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; -import type { Subnet } from "~/utils/types"; - -interface DelegatedSubnet extends Subnet { +export interface DelegatedSubnet { + id: number; + name: string; percentage: number; + founderAddress: string; } interface DelegateState { @@ -44,8 +45,8 @@ export const useDelegateSubnetStore = create()( getTotalPercentage: () => { return get().delegatedSubnets.reduce((sum, m) => sum + m.percentage, 0); }, - setDelegatedSubnetsFromDB: (Subnets) => - set(() => ({ delegatedSubnets: Subnets, originalSubnets: Subnets })), + setDelegatedSubnetsFromDB: (subnets) => + set(() => ({ delegatedSubnets: subnets, originalSubnets: subnets })), updateOriginalSubnets: () => set((state) => ({ originalSubnets: [...state.delegatedSubnets] })), hasUnsavedChanges: () => { @@ -53,11 +54,11 @@ export const useDelegateSubnetStore = create()( if (state.delegatedSubnets.length !== state.originalSubnets.length) { return true; } - return state.delegatedSubnets.some((Subnet, index) => { + return state.delegatedSubnets.some((subnet, index) => { const originalSubnet = state.originalSubnets[index]; return ( - Subnet.id !== originalSubnet?.id || - Subnet.percentage !== originalSubnet.percentage + subnet.id !== originalSubnet?.id || + subnet.percentage !== originalSubnet.percentage ); }); }, diff --git a/apps/commune-validator/src/utils/types.ts b/apps/commune-validator/src/utils/types.ts index 2858c224..de5eea2f 100644 --- a/apps/commune-validator/src/utils/types.ts +++ b/apps/commune-validator/src/utils/types.ts @@ -6,6 +6,10 @@ export type Subnet = NonNullable< inferProcedureOutput >; +export type Module = NonNullable< + inferProcedureOutput +>; + export type ReportReason = NonNullable< inferProcedureOutput >["reason"]; diff --git a/apps/commune-worker/src/workers/subnet-fetcher.ts b/apps/commune-worker/src/workers/subnet-fetcher.ts index d1fdd325..8fd44aa8 100644 --- a/apps/commune-worker/src/workers/subnet-fetcher.ts +++ b/apps/commune-worker/src/workers/subnet-fetcher.ts @@ -27,7 +27,7 @@ export async function subnetFetcherWorker(props: WorkerProps) { SubnetToDatabase(subnet, props.lastBlock.blockNumber), ); log( - `Block ${props.lastBlock.blockNumber}: upserting ${subnetData.length} modules`, + `Block ${props.lastBlock.blockNumber}: upserting ${subnetData.length} subnets`, ); await upsertSubnetData(subnetData); diff --git a/packages/utils/index.ts b/packages/utils/index.ts index dea2712b..8efeb5db 100644 --- a/packages/utils/index.ts +++ b/packages/utils/index.ts @@ -1,30 +1,29 @@ +import { BN } from "@polkadot/util"; import { CID } from "multiformats/cid"; import { match } from "rustie"; import { AssertionError } from "tsafe"; import type { AnyTuple, + Api, Codec, CustomDaoMetadata, CustomDataError, CustomMetadata, DaoApplications, Entry, - OptionalProperties, Proposal, RawEntry, Result, SS58Address, StorageKey, - SubspaceModule, ZodSchema, - Api } from "@commune-ts/types"; import { CUSTOM_METADATA_SCHEMA, DAO_APPLICATIONS_SCHEMA, PROPOSAL_SCHEMA, - URL_SCHEMA + URL_SCHEMA, } from "@commune-ts/types"; /** @@ -113,9 +112,7 @@ export function bigintDivision(a: bigint, b: bigint, precision = 8n): number { return (Number(a) * Number(base)) / Number(b) / baseNum; } -import { BN } from '@polkadot/util'; - -const NANO_MULTIPLIER = new BN('1000000000'); +const NANO_MULTIPLIER = new BN("1000000000"); /** * Converts a nano value to its standard unit representation @@ -123,12 +120,15 @@ const NANO_MULTIPLIER = new BN('1000000000'); * @param decimals - Number of decimal places to round to (default: 6) * @returns The value in standard units as a string */ -export function fromNano(nanoValue: number | string | bigint | BN, decimals = 9): string { +export function fromNano( + nanoValue: number | string | bigint | BN, + decimals = 9, +): string { const bnValue = new BN(nanoValue.toString()); const integerPart = bnValue.div(NANO_MULTIPLIER); const fractionalPart = bnValue.mod(NANO_MULTIPLIER); - const fractionalStr = fractionalPart.toString().padStart(9, '0'); + const fractionalStr = fractionalPart.toString().padStart(9, "0"); const roundedFractionalStr = fractionalStr.slice(0, decimals); return `${integerPart.toString()}.${roundedFractionalStr}`; @@ -140,19 +140,23 @@ export function fromNano(nanoValue: number | string | bigint | BN, decimals = 9) * @returns The value in nano units as a BN */ export function toNano(standardValue: number | string): BN { - const [integerPart, fractionalPart = ''] = standardValue.toString().split('.'); - const paddedFractionalPart = fractionalPart.padEnd(9, '0'); + const [integerPart, fractionalPart = ""] = standardValue + .toString() + .split("."); + const paddedFractionalPart = fractionalPart.padEnd(9, "0"); const nanoValue = `${integerPart}${paddedFractionalPart}`; return new BN(nanoValue); } - export function formatToken(nano: number | bigint, decimalPlaces = 2): string { const fullPrecisionAmount = fromNano(nano).toString(); - const [integerPart = '0', fractionalPart = ''] = fullPrecisionAmount.split('.'); + const [integerPart = "0", fractionalPart = ""] = + fullPrecisionAmount.split("."); - const formattedIntegerPart = Number(integerPart).toLocaleString('en-US'); - const roundedFractionalPart = fractionalPart.slice(0, decimalPlaces).padEnd(decimalPlaces, '0'); + const formattedIntegerPart = Number(integerPart).toLocaleString("en-US"); + const roundedFractionalPart = fractionalPart + .slice(0, decimalPlaces) + .padEnd(decimalPlaces, "0"); return `${formattedIntegerPart}.${roundedFractionalPart}`; } @@ -263,24 +267,43 @@ export function getExpirationTime( } export interface ChainEntry { - getMapModules(netuid?: number): Record; } - export type SubspacePalletName = - | "subspaceModule" | "governanceModule" | "subnetEmissionModule"; - + | "subspaceModule" + | "governanceModule" + | "subnetEmissionModule"; export type SubspaceStorageName = - | "emission" | "incentive" | "dividends" | "lastUpdate" - | "metadata" | "registrationBlock" | "name" | "address" - | "keys" | "subnetNames" | "immunityPeriod" | "minAllowedWeights" - | "maxAllowedWeights" | "tempo" | "maxAllowedUids" | "founder" - | "founderShare" | "incentiveRatio" | "trustRatio" | "maxWeightAge" - | "bondsMovingAverage" | "maximumSetWeightCallsPerEpoch" | "minValidatorStake" - | "maxAllowedValidators" | "moduleBurnConfig" | "subnetMetadata" - | "subnetGovernanceConfig" | "subnetEmission"; + | "emission" + | "incentive" + | "dividends" + | "lastUpdate" + | "metadata" + | "registrationBlock" + | "name" + | "address" + | "keys" + | "subnetNames" + | "immunityPeriod" + | "minAllowedWeights" + | "maxAllowedWeights" + | "tempo" + | "maxAllowedUids" + | "founder" + | "founderShare" + | "incentiveRatio" + | "trustRatio" + | "maxWeightAge" + | "bondsMovingAverage" + | "maximumSetWeightCallsPerEpoch" + | "minValidatorStake" + | "maxAllowedValidators" + | "moduleBurnConfig" + | "subnetMetadata" + | "subnetGovernanceConfig" + | "subnetEmission"; // TODO: add MinimumAllowedStake, stakeFrom @@ -288,7 +311,10 @@ export function standardizeUidToSS58address( outerRecord: Record>, uidToKey: Record, ): Record> { - const processedRecord: Record> = {} as Record>; + const processedRecord: Record< + T, + Record + > = {} as Record>; const entries = Object.entries(outerRecord) as [T, Record][]; for (const [outerKey, innerRecord] of entries) { @@ -311,26 +337,45 @@ export function standardizeUidToSS58address( return processedRecord; } - type StorageTypes = "VecMapping" | "DoubleMap" | "SimpleMap"; export function getSubspaceStorageMappingKind( - prop: SubspaceStorageName + prop: SubspaceStorageName, ): StorageTypes | null { - const vecProps: SubspaceStorageName[] = [ - "emission", "incentive", "dividends", "lastUpdate" + "emission", + "incentive", + "dividends", + "lastUpdate", ]; const doubleMapProps: SubspaceStorageName[] = [ - "metadata", "registrationBlock", "name", "address", "keys" + "metadata", + "registrationBlock", + "name", + "address", + "keys", ]; const simpleMapProps: SubspaceStorageName[] = [ - "minAllowedWeights", "maxWeightAge", "maxAllowedWeights", "trustRatio", - "tempo", "founderShare", "subnetNames", "immunityPeriod", "maxAllowedUids", - "founder", "incentiveRatio", "bondsMovingAverage", - "maximumSetWeightCallsPerEpoch", "minValidatorStake", "maxAllowedValidators", - "moduleBurnConfig", "subnetMetadata", "subnetGovernanceConfig", "subnetEmission" - ] + "minAllowedWeights", + "maxWeightAge", + "maxAllowedWeights", + "trustRatio", + "tempo", + "founderShare", + "subnetNames", + "immunityPeriod", + "maxAllowedUids", + "founder", + "incentiveRatio", + "bondsMovingAverage", + "maximumSetWeightCallsPerEpoch", + "minValidatorStake", + "maxAllowedValidators", + "moduleBurnConfig", + "subnetMetadata", + "subnetGovernanceConfig", + "subnetEmission", + ]; const mapping = { VecMapping: vecProps, DoubleMap: doubleMapProps, @@ -347,7 +392,8 @@ export async function getPropsToMap( api: Api, netuid?: number, ): Promise> { - const mapped_prop_entries: Record = {} as Record + const mapped_prop_entries: Record = + {} as Record; for (const [palletName, storageNames] of Object.entries(props)) { const asyncOperations = storageNames.map(async (storageName) => { @@ -363,7 +409,8 @@ export async function getPropsToMap( } mapped_prop_entries[storageName] = new StorageVecMap(entries); } else if (value === "DoubleMap") { - const entries = await api.query[palletName]?.[storageName]?.entries(netuid); + const entries = + await api.query[palletName]?.[storageName]?.entries(netuid); if (entries === undefined) { console.log(`No entries for ${palletName}.${storageName}`); @@ -390,7 +437,7 @@ export async function getPropsToMap( } export class StorageVecMap implements ChainEntry { - constructor(private readonly entry: [StorageKey, Codec][]) { } + constructor(private readonly entry: [StorageKey, Codec][]) {} getMapModules(netuid: number) { if (netuid === undefined) { @@ -403,13 +450,12 @@ export class StorageVecMap implements ChainEntry { values.map((value, index) => [index, value]), ); return modules_map; - } - else return {}; + } else return {}; } } export class SimpleMap implements ChainEntry { - constructor(private readonly entry: [StorageKey, Codec][]) { } + constructor(private readonly entry: [StorageKey, Codec][]) {} getMapModules() { const modules_map = Object.fromEntries( @@ -420,11 +466,11 @@ export class SimpleMap implements ChainEntry { } export class DoubleMapEntries implements ChainEntry { - constructor(private readonly entries: [StorageKey, Codec][]) { } + constructor(private readonly entries: [StorageKey, Codec][]) {} getMapModules() { const moduleIdToPropValue: Record = {}; - this.entries.forEach(entry => { + this.entries.forEach((entry) => { const moduleCodec = entry[1]; const moduleId = entry[0].args[1]?.toPrimitive() as number; moduleIdToPropValue[moduleId] = moduleCodec.toPrimitive() as string; @@ -433,7 +479,6 @@ export class DoubleMapEntries implements ChainEntry { } } - export function parseAddress(valueRaw: Codec): DaoApplications | null { const value = valueRaw.toPrimitive(); const validated = DAO_APPLICATIONS_SCHEMA.safeParse(value); @@ -465,7 +510,7 @@ export const paramNameToDisplayName = (paramName: string): string => { return ( // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition PARAM_FIELD_DISPLAY_NAMES[ - paramName as keyof typeof PARAM_FIELD_DISPLAY_NAMES + paramName as keyof typeof PARAM_FIELD_DISPLAY_NAMES ] ?? paramName ); }; @@ -555,4 +600,4 @@ export function flattenResult(x: Result): T | null { return null; }, }); -} \ No newline at end of file +}