diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx
index 046c43b5..5c7447fc 100644
--- a/apps/frontend/src/App.tsx
+++ b/apps/frontend/src/App.tsx
@@ -3,12 +3,13 @@ import { createBrowserRouter, RouterProvider, Outlet, useLocation } from "react-
import { Proposal } from "./pages/Proposal/Proposal";
import { Proposals } from "./pages/Proposals";
import { CreateProposal } from "./pages/CreateProposal/CreateProposal";
+import { AdminDashboard } from "./pages/Admin/AdminDashboard";
import { Footer } from "./components/footer/Footer";
import { ScrollToTop } from "./components/ui/ScrollToTop";
import { useVoteCastInvalidate } from "./hooks/useVoteCastInvalidate";
import { useProposalCreatedInvalidate } from "./hooks/useProposalCreatedInvalidate";
-const excludedRoutes = ["/create-proposal"];
+const excludedRoutes = ["/create-proposal", "/admin"];
function Layout() {
const { pathname } = useLocation();
@@ -46,6 +47,10 @@ const router = createBrowserRouter([
path: "create-proposal",
element: ,
},
+ {
+ path: "admin",
+ element: ,
+ },
],
},
]);
diff --git a/apps/frontend/src/components/ui/InputMessage.tsx b/apps/frontend/src/components/ui/InputMessage.tsx
index 45d6f9d1..57f26104 100644
--- a/apps/frontend/src/components/ui/InputMessage.tsx
+++ b/apps/frontend/src/components/ui/InputMessage.tsx
@@ -15,5 +15,9 @@ export const InputMessage = ({ error, message }: InputMessageProps) => {
{error}
);
- return {message};
+ return (
+
+ {message}
+
+ );
};
diff --git a/apps/frontend/src/constants.tsx b/apps/frontend/src/constants.tsx
index 7c835545..657ac189 100644
--- a/apps/frontend/src/constants.tsx
+++ b/apps/frontend/src/constants.tsx
@@ -14,3 +14,5 @@ export const IconByVote = {
[SingleChoiceEnum.FOR]: ThumbsUpIcon,
[SingleChoiceEnum.ABSTAIN]: AbstainIcon,
};
+
+export const VALIDATOR_STAKED_VET_REQUIREMENT = BigInt("25000000000000000000000000"); // 25 million VET
diff --git a/apps/frontend/src/hooks/useAdminUserRoles.ts b/apps/frontend/src/hooks/useAdminUserRoles.ts
new file mode 100644
index 00000000..dd305e9f
--- /dev/null
+++ b/apps/frontend/src/hooks/useAdminUserRoles.ts
@@ -0,0 +1,69 @@
+import { executeMultipleClauses } from "@/utils/contract";
+import { getConfig } from "@repo/config";
+import { VeVote__factory } from "@vechain/vevote-contracts";
+import { useQuery } from "@tanstack/react-query";
+import { useCallback } from "react";
+
+const ROLES = [
+ "DEFAULT_ADMIN_ROLE",
+ "EXECUTOR_ROLE",
+ "SETTINGS_MANAGER_ROLE",
+ "NODE_WEIGHT_MANAGER_ROLE",
+ "UPGRADER_ROLE",
+ "WHITELISTED_ROLE",
+ "WHITELIST_ADMIN_ROLE",
+] as const;
+
+const contractAddress = getConfig(import.meta.env.VITE_APP_ENV).vevoteContractAddress;
+const contractInterface = VeVote__factory.createInterface();
+
+export const useUserAdminRoles = (userAddress: string) => {
+ const checkUserRoles = useCallback(async () => {
+ try {
+ const roleHashMethods = ROLES.map(role => ({
+ method: role,
+ args: [],
+ }));
+
+ const roleHashResults = await executeMultipleClauses({
+ contractAddress,
+ contractInterface,
+ methodsWithArgs: roleHashMethods,
+ });
+
+ const roleHashes = roleHashResults
+ .map((result, index) => ({
+ name: ROLES[index],
+ hash: result?.success ? result.result.plain : null,
+ }))
+ .filter(role => role.hash);
+
+ const hasRoleMethods = roleHashes.map(role => ({
+ method: "hasRole" as const,
+ args: [role.hash, userAddress],
+ }));
+
+ const hasRoleResults = await executeMultipleClauses({
+ contractAddress,
+ contractInterface,
+ methodsWithArgs: hasRoleMethods,
+ });
+
+ const userRoles = roleHashes
+ .filter((_, index) => hasRoleResults[index]?.success && hasRoleResults[index].result.plain === true)
+ .map(role => role.name);
+
+ return userRoles;
+ } catch (error) {
+ console.error("Error checking user roles:", error);
+ return [];
+ }
+ }, [userAddress]);
+
+ return useQuery({
+ queryKey: ["userRoles", userAddress],
+ queryFn: checkUserRoles,
+ enabled: Boolean(userAddress),
+ staleTime: 30000,
+ });
+};
diff --git a/apps/frontend/src/hooks/useContractRoles.ts b/apps/frontend/src/hooks/useContractRoles.ts
new file mode 100644
index 00000000..88c72ff3
--- /dev/null
+++ b/apps/frontend/src/hooks/useContractRoles.ts
@@ -0,0 +1,85 @@
+import { executeMultipleClauses } from "@/utils/contract";
+import { getConfig } from "@repo/config";
+import { VeVote__factory } from "@vechain/vevote-contracts";
+import { NodeManagement__factory, StargateNFT__factory } from "@vechain/vevote-contracts/typechain-types";
+import { useQuery } from "@tanstack/react-query";
+import { useCallback } from "react";
+import { useWallet } from "@vechain/vechain-kit";
+import { CONTRACT_CONFIGS, ContractType } from "@/pages/Admin/constants/contracts";
+
+const getContractInterface = (contractType: ContractType) => {
+ switch (contractType) {
+ case "vevote":
+ return VeVote__factory.createInterface();
+ case "nodeManagement":
+ return NodeManagement__factory.createInterface();
+ case "stargate":
+ return StargateNFT__factory.createInterface();
+ default:
+ return VeVote__factory.createInterface();
+ }
+};
+
+export const useContractRoles = (contractType: ContractType = "vevote") => {
+ const { account } = useWallet();
+ const config = CONTRACT_CONFIGS[contractType];
+ const contractAddress = getConfig(import.meta.env.VITE_APP_ENV)[config.addressKey];
+ const contractInterface = getContractInterface(contractType);
+
+ const checkUserRoles = useCallback(async () => {
+ if (!account?.address) return [];
+
+ try {
+ const roleHashMethods = config.roles.map(role => ({
+ method: role,
+ args: [],
+ }));
+
+ const roleHashResults = await executeMultipleClauses({
+ contractAddress,
+ contractInterface,
+ methodsWithArgs: roleHashMethods,
+ });
+
+ const roleHashes = roleHashResults
+ .map((result, index) => ({
+ name: config.roles[index],
+ hash: result?.success ? result.result.plain : null,
+ }))
+ .filter(role => role.hash);
+
+ const hasRoleMethods = roleHashes.map(role => ({
+ method: "hasRole" as const,
+ args: [role.hash, account.address],
+ }));
+
+ const hasRoleResults = await executeMultipleClauses({
+ contractAddress,
+ contractInterface,
+ methodsWithArgs: hasRoleMethods,
+ });
+
+ const userRoles = roleHashes
+ .filter((_, index) => hasRoleResults[index]?.success && hasRoleResults[index].result.plain === true)
+ .map(role => role.name);
+
+ return userRoles;
+ } catch (error) {
+ console.error("Error checking user roles:", error);
+ return [];
+ }
+ }, [account?.address, contractAddress, config.roles, contractInterface]);
+
+ const query = useQuery({
+ queryKey: ["contractRoles", contractType, account?.address],
+ queryFn: checkUserRoles,
+ enabled: Boolean(account?.address),
+ staleTime: 30000,
+ });
+
+ return {
+ defaultRoles: config.roles,
+ roles: query.data || [],
+ isLoading: query.isLoading,
+ };
+};
diff --git a/apps/frontend/src/hooks/useFormatTime.ts b/apps/frontend/src/hooks/useFormatTime.ts
new file mode 100644
index 00000000..ea1fad25
--- /dev/null
+++ b/apps/frontend/src/hooks/useFormatTime.ts
@@ -0,0 +1,29 @@
+import { useCallback } from "react";
+import dayjs from "dayjs";
+import duration from "dayjs/plugin/duration";
+import { useI18nContext } from "@/i18n/i18n-react";
+
+dayjs.extend(duration);
+
+export const useFormatTime = () => {
+ const { LL } = useI18nContext();
+
+ const formatTime = useCallback((seconds: number) => {
+ if (seconds < 60) {
+ return LL.common.time.seconds({ count: seconds });
+ } else if (seconds < 3600) {
+ const minutes = Math.floor(seconds / 60);
+ return LL.common.time.minutes({ count: minutes });
+ } else if (seconds < 86400) {
+ const hours = Math.floor(seconds / 3600);
+ return LL.common.time.hours({ count: hours });
+ } else {
+ const days = Math.floor(seconds / 86400);
+ return LL.common.time.days({ count: days });
+ }
+ }, [LL.common.time]);
+
+ return {
+ formatTime,
+ };
+};
\ No newline at end of file
diff --git a/apps/frontend/src/hooks/useGovernanceSettings.ts b/apps/frontend/src/hooks/useGovernanceSettings.ts
new file mode 100644
index 00000000..17cdcb81
--- /dev/null
+++ b/apps/frontend/src/hooks/useGovernanceSettings.ts
@@ -0,0 +1,94 @@
+import { getConfig } from "@repo/config";
+import { VeVote__factory } from "@vechain/vevote-contracts";
+import { EnhancedClause } from "@vechain/vechain-kit";
+import { useVevoteSendTransaction } from "@/utils/hooks/useVevoteSendTransaction";
+import { useCallback } from "react";
+
+type GovernanceSettingsProps = {
+ updateQuorumNumerator?: number;
+ setMinVotingDelay?: number;
+ setMinVotingDuration?: number;
+ setMaxVotingDuration?: number;
+ setMinStakedVetAmount?: bigint;
+};
+
+const contractAddress = getConfig(import.meta.env.VITE_APP_ENV).vevoteContractAddress;
+const contractInterface = VeVote__factory.createInterface();
+
+export const useGovernanceSettings = () => {
+ const buildClauses = useCallback((props: GovernanceSettingsProps) => {
+ const clauses: EnhancedClause[] = [];
+
+ try {
+ const baseClause = {
+ to: contractAddress,
+ value: 0,
+ };
+
+ if (props.updateQuorumNumerator !== undefined) {
+ const clause = {
+ ...baseClause,
+ data: contractInterface.encodeFunctionData("updateQuorumNumerator", [props.updateQuorumNumerator]),
+ abi: JSON.parse(JSON.stringify(contractInterface.getFunction("updateQuorumNumerator"))),
+ comment: `Update quorum numerator to ${props.updateQuorumNumerator}`,
+ };
+ clauses.push(clause as EnhancedClause);
+ }
+
+ if (props.setMinVotingDelay !== undefined) {
+ const blocksValue = Math.floor(props.setMinVotingDelay / 10);
+ const clause = {
+ ...baseClause,
+ data: contractInterface.encodeFunctionData("setMinVotingDelay", [blocksValue]),
+ abi: JSON.parse(JSON.stringify(contractInterface.getFunction("setMinVotingDelay"))),
+ comment: `Set min voting delay to ${props.setMinVotingDelay} seconds (${blocksValue} blocks)`,
+ };
+ clauses.push(clause as EnhancedClause);
+ }
+
+ if (props.setMinVotingDuration !== undefined) {
+ const blocksValue = Math.floor(props.setMinVotingDuration / 10);
+ const clause = {
+ ...baseClause,
+ data: contractInterface.encodeFunctionData("setMinVotingDuration", [blocksValue]),
+ abi: JSON.parse(JSON.stringify(contractInterface.getFunction("setMinVotingDuration"))),
+ comment: `Set min voting duration to ${props.setMinVotingDuration} seconds (${blocksValue} blocks)`,
+ };
+ clauses.push(clause as EnhancedClause);
+ }
+
+ if (props.setMaxVotingDuration !== undefined) {
+ const blocksValue = Math.floor(props.setMaxVotingDuration / 10);
+ const clause = {
+ ...baseClause,
+ data: contractInterface.encodeFunctionData("setMaxVotingDuration", [blocksValue]),
+ abi: JSON.parse(JSON.stringify(contractInterface.getFunction("setMaxVotingDuration"))),
+ comment: `Set max voting duration to ${props.setMaxVotingDuration} seconds (${blocksValue} blocks)`,
+ };
+ clauses.push(clause as EnhancedClause);
+ }
+
+ if (props.setMinStakedVetAmount !== undefined) {
+ const clause = {
+ ...baseClause,
+ data: contractInterface.encodeFunctionData("setMinStakedVetAmount", [props.setMinStakedVetAmount]),
+ abi: JSON.parse(JSON.stringify(contractInterface.getFunction("setMinStakedVetAmount"))),
+ comment: `Set minimum staked VET amount to ${props.setMinStakedVetAmount.toString()}`,
+ };
+ clauses.push(clause as EnhancedClause);
+ }
+
+ return clauses;
+ } catch (error) {
+ console.error(error);
+ return [];
+ }
+ }, []);
+
+ return useVevoteSendTransaction({
+ clauseBuilder: buildClauses,
+ delayedRefetchKeys: [["veVoteInfo"]],
+ refetchDelay: 500,
+ maxRetries: 1,
+ });
+};
diff --git a/apps/frontend/src/hooks/useLevelMultipliers.ts b/apps/frontend/src/hooks/useLevelMultipliers.ts
new file mode 100644
index 00000000..a7833cc6
--- /dev/null
+++ b/apps/frontend/src/hooks/useLevelMultipliers.ts
@@ -0,0 +1,45 @@
+import { getConfig } from "@repo/config";
+import { VeVote__factory } from "@vechain/vevote-contracts";
+import { EnhancedClause } from "@vechain/vechain-kit";
+import { useVevoteSendTransaction } from "@/utils/hooks/useVevoteSendTransaction";
+import { useCallback } from "react";
+
+type LevelMultipliersProps = {
+ updateLevelIdMultipliers: number[];
+};
+
+const contractAddress = getConfig(import.meta.env.VITE_APP_ENV).vevoteContractAddress;
+const contractInterface = VeVote__factory.createInterface();
+
+export const useLevelMultipliers = () => {
+ const buildClauses = useCallback((props: LevelMultipliersProps) => {
+ const clauses: EnhancedClause[] = [];
+
+ try {
+ const baseClause = {
+ to: contractAddress,
+ value: 0,
+ };
+
+ const clause = {
+ ...baseClause,
+ data: contractInterface.encodeFunctionData("updateLevelIdMultipliers", [props.updateLevelIdMultipliers]),
+ abi: JSON.parse(JSON.stringify(contractInterface.getFunction("updateLevelIdMultipliers"))),
+ comment: `Update level ID multipliers: [${props.updateLevelIdMultipliers.join(", ")}]`,
+ };
+ clauses.push(clause as EnhancedClause);
+
+ return clauses;
+ } catch (error) {
+ console.error(error);
+ return [];
+ }
+ }, []);
+
+ return useVevoteSendTransaction({
+ clauseBuilder: buildClauses,
+ delayedRefetchKeys: [["veVoteInfo"], ["levelMultipliers"]],
+ refetchDelay: 500,
+ maxRetries: 1,
+ });
+};
diff --git a/apps/frontend/src/hooks/useRoleManagement.ts b/apps/frontend/src/hooks/useRoleManagement.ts
new file mode 100644
index 00000000..669eb90c
--- /dev/null
+++ b/apps/frontend/src/hooks/useRoleManagement.ts
@@ -0,0 +1,55 @@
+import { getConfig } from "@repo/config";
+import { VeVote__factory } from "@vechain/vevote-contracts";
+import { EnhancedClause } from "@vechain/vechain-kit";
+import { useVevoteSendTransaction } from "@/utils/hooks/useVevoteSendTransaction";
+import { useCallback } from "react";
+import { ROLES } from "@/pages/Admin/components/Users/UserManagementCard";
+
+type RoleManagementProps = {
+ action: "grant" | "revoke";
+ role: (typeof ROLES)[number];
+ account: string;
+};
+
+const contractAddress = getConfig(import.meta.env.VITE_APP_ENV).vevoteContractAddress;
+const contractInterface = VeVote__factory.createInterface();
+
+export const useRoleManagement = () => {
+ const buildClauses = useCallback((props: RoleManagementProps) => {
+ const clauses: EnhancedClause[] = [];
+
+ try {
+ const baseClause = {
+ to: contractAddress,
+ value: 0,
+ };
+
+ if (props.action === "grant") {
+ const clause = {
+ ...baseClause,
+ data: contractInterface.encodeFunctionData("grantRole", [props.role, props.account]),
+ abi: JSON.parse(JSON.stringify(contractInterface.getFunction("grantRole"))),
+ comment: `Grant role to ${props.account}`,
+ };
+ clauses.push(clause as EnhancedClause);
+ } else if (props.action === "revoke") {
+ const clause = {
+ ...baseClause,
+ data: contractInterface.encodeFunctionData("revokeRole", [props.role, props.account]),
+ abi: JSON.parse(JSON.stringify(contractInterface.getFunction("revokeRole"))),
+ comment: `Revoke role from ${props.account}`,
+ };
+ clauses.push(clause as EnhancedClause);
+ }
+
+ return clauses;
+ } catch (error) {
+ console.error(error);
+ return [];
+ }
+ }, []);
+
+ return useVevoteSendTransaction({
+ clauseBuilder: buildClauses,
+ });
+};
diff --git a/apps/frontend/src/i18n/en/index.ts b/apps/frontend/src/i18n/en/index.ts
index ec69906b..c9403b8a 100644
--- a/apps/frontend/src/i18n/en/index.ts
+++ b/apps/frontend/src/i18n/en/index.ts
@@ -68,6 +68,14 @@ const en = {
stargate: "StarGate",
learn_how_voting_power: "How voting power is obtained",
discuss_on_discourse: "Join the discussion on Discourse",
+ common: {
+ time: {
+ seconds: "{count:number} {count|{1: second, *: seconds}}",
+ minutes: "{count:number} {count|{1: minute, *: minutes}}",
+ hours: "{count:number} {count|{1: hour, *: hours}}",
+ days: "{count:number} {count|{1: day, *: days}}",
+ },
+ },
datepicker: {
select_date: "Select date",
previous_month: "Previous month",
@@ -126,6 +134,7 @@ const en = {
field_errors: {
required: "Required",
invalid_format: "Invalid format",
+ invalid_address: "Please enter a valid address",
end_before_start: "The end date must be after the start date",
end_before_today: "The end date must be in the future",
start_after_today: "The start date must be in the future",
@@ -422,6 +431,222 @@ const en = {
governance_charter: "Governance Charter",
},
},
+ admin: {
+ title: "Admin Dashboard",
+ tabs: {
+ contracts: "Contracts",
+ utils: "Utils",
+ users: "Users",
+ governance_settings: "Governance Settings",
+ voting_power_timepoint: "Voting Power Query",
+ },
+ contracts: {
+ vevote: "VeVote",
+ node_management: "Node Management",
+ stargate_nodes: "Stargate Nodes",
+ },
+ vevote_contract: {
+ contract_address: "Contract Address:",
+ title: "VeVote Contract Information",
+ loading: "Loading VeVote Contract Information...",
+ error: "Error loading VeVote contract data: {error:string}",
+ no_data: "No VeVote contract data available",
+ contract_version: "Contract Version",
+ quorum_numerator: "Quorum Numerator",
+ quorum_denominator: "Quorum Denominator",
+ min_voting_delay: "Min Voting Delay",
+ min_voting_duration: "Min Voting Duration",
+ max_voting_duration: "Max Voting Duration",
+ min_staked_amount: "Min Staked Amount",
+ quorum_percentage: "{percentage:number}% required",
+ contract_info_title: "Contract Information",
+ },
+ node_management: {
+ title: "Node Management Contract Information",
+ help_text: "Enter a wallet address to view detailed node ownership and delegation information for that account.",
+ user_address_label: "User Address",
+ user_address_placeholder: "Enter user address (0x...)",
+ load_button: "Load User Node Info",
+ loading_button: "Loading...",
+ loading_text: "Loading user node information...",
+ node_info_title: "Node Information for {address:string}",
+ is_node_holder: "Is Node Holder",
+ is_node_delegator: "Is Node Delegator",
+ owned_nodes: "Owned Nodes",
+ managed_nodes: "Managed Nodes",
+ ids_label: "IDs: {ids:string}",
+ yes: "Yes",
+ no: "No",
+ error: "Error loading node data: {error:string}",
+ card_title: "Node Information",
+ results_for: "Results for {address:string}",
+ no_results: "No node information available for this address",
+ methods_title: "Available Methods",
+ methods_description:
+ "This component demonstrates the NodeManagementService functionality. You can extend it to show additional statistics like total nodes, delegation stats, etc.",
+ },
+ stargate_nodes: {
+ title: "Stargate NFT Contract Information",
+ loading: "Loading Stargate NFT Information...",
+ error: "Error loading Stargate NFT data: {error:string}",
+ no_data: "No Stargate NFT data available",
+ total_supply: "Total Supply",
+ available_levels: "Available Levels",
+ level_ids: "Level IDs: {ids:string}",
+ level_details_title: "Level Details",
+ table: {
+ level: "Level",
+ name: "Name",
+ is_x_node: "Is X-Node",
+ maturity_blocks: "Maturity Blocks",
+ vet_required: "VET Required",
+ circulating: "Circulating",
+ cap: "Cap",
+ },
+ yes: "Yes",
+ no: "No",
+ not_available: "N/A",
+ contract_info_title: "Contract Information",
+ contract_description:
+ "This displays comprehensive information about the Stargate NFT contract including level configurations, supply information, and staking requirements.",
+ },
+ format_seconds: "{number:number}s",
+ format_minutes_seconds: "{minutes:number} min ({seconds:number}s)",
+ format_days: "{number:number} days",
+ vet_format: "{amount:string} VET",
+ governance_settings: {
+ title: "Governance Settings",
+ description: "Configure VeVote governance parameters",
+ quorum_numerator_label: "Quorum Numerator",
+ quorum_numerator_help: "Required votes numerator (current: {current:number})",
+ min_voting_delay_label: "Min Voting Delay",
+ min_voting_delay_help: "Minimum delay before voting starts (in blocks)",
+ min_voting_duration_label: "Min Voting Duration",
+ min_voting_duration_help: "Minimum voting duration (in blocks)",
+ max_voting_duration_label: "Max Voting Duration",
+ max_voting_duration_help: "Maximum voting duration (in blocks)",
+ min_staked_vet_amount_label: "Min Staked VET Amount",
+ min_staked_vet_amount_help: "Minimum VET amount required to stake",
+ level_multipliers_label: "Level ID Multipliers",
+ level_multipliers_help: "Voting weight multipliers for each level ID (scaled by 100)",
+ validator_multiplier: "Validator",
+ strength: "Strength",
+ thunder: "Thunder",
+ mjolnir: "Mjolnir",
+ vethor_x: "VeThor X",
+ strength_x: "Strength X",
+ thunder_x: "Thunder X",
+ mjolnir_x: "Mjolnir X",
+ dawn: "Dawn Node",
+ lightning: "Lightning Node",
+ flash: "Flash Node",
+ level_id: "Level ID",
+ node_name: "Node Name",
+ current_multiplier: "Current",
+ new_multiplier: "New Value",
+ update_settings: "Update Settings",
+ update_governance_settings: "Update Governance Settings",
+ update_multipliers: "Update Multipliers",
+ updating: "Updating...",
+ success_title: "Settings Updated Successfully",
+ success_description: "Governance settings have been updated and are now active.",
+ error_title: "Failed to Update Settings",
+ error_description: "There was an error updating the governance settings: {error:string}",
+ invalid_range: "Value must be between {min:number} and {max:number}",
+ required_field: "This field is required",
+ current_value: "Current: {value:string}",
+ },
+ user_management: {
+ title: "User Management",
+ description: "Grant or revoke roles for VeVote governance",
+ user_address_label: "User Address",
+ user_address_placeholder: "Enter user address (0x...)",
+ role_label: "Role",
+ role_placeholder: "Select a role",
+ current_roles_label: "Current Roles:",
+ checking_roles: "Checking roles...",
+ no_roles_assigned: "No roles assigned",
+ },
+ role_users: {
+ title: "Role Users",
+ help_text: "Select a role to view all users who have been granted that specific role.",
+ role_label: "Role",
+ role_placeholder: "Select a role to query",
+ query_button: "Query Role Users",
+ loading: "Loading...",
+ loading_text: "Fetching users with selected role...",
+ results_title: "Users with Role",
+ user_count: "{count:number} {count|{1: user, *: users}}",
+ role_selected: "Role: {role:string}",
+ granted_at: "Granted: {date:string}",
+ view_tx: "View TX",
+ no_users: "No users found with this role",
+ scrollable_hint: "Scroll to see more users",
+ error_description: "Error fetching role users: {error:string}",
+ },
+ user_role_checker: {
+ title: "Your Permissions",
+ connect_wallet_message: "Connect wallet to see your roles",
+ checking_roles: "Checking your roles...",
+ },
+ voting_power_timepoint: {
+ address: "Address:",
+ timepoint: "Timepoint:",
+ master_address: "Master Address:",
+ title: "Voting Power at Timepoint",
+ description: "Query historical voting power for any wallet at a specific timepoint",
+ address_label: "Wallet Address",
+ address_placeholder: "Enter wallet address (0x...)",
+ timepoint_label: "Timepoint",
+ timepoint_placeholder: "Enter timepoint",
+ master_address_label: "Master Address",
+ master_address_placeholder: "Enter master address for validator power (0x...)",
+ query_button: "Query Voting Power",
+ querying: "Querying...",
+ results_title: "Voting Power Results",
+ node_power_label: "Node-based Power:",
+ validator_power_label: "Validator Power:",
+ total_power_label: "Total Power:",
+ no_results: "No results to display",
+ invalid_address: "Please enter a valid address",
+ invalid_timepoint: "Please enter a valid timepoint/block number",
+ address_required: "Wallet address is required",
+ timepoint_required: "Timepoint is required",
+ error_title: "Query Failed",
+ error_description: "There was an error querying voting power: {error:string}",
+ help_text:
+ "Enter a wallet address and timepoint to view historical voting power. Optionally provide a master address to check validator-delegated power.",
+ },
+ unknown_error: "Unknown error",
+ sensitive_operation_warning: "This operation is sensitive. Please use with caution.",
+ your_permissions: "Your Permissions",
+ common_roles: {
+ DEFAULT_ADMIN_ROLE: "Default Admin",
+ EXECUTOR_ROLE: "Executor",
+ SETTINGS_MANAGER_ROLE: "Settings Manager",
+ NODE_WEIGHT_MANAGER_ROLE: "Node Weight Manager",
+ UPGRADER_ROLE: "Upgrader",
+ WHITELISTED_ROLE: "Whitelisted",
+ WHITELIST_ADMIN_ROLE: "Whitelist Admin",
+ PAUSER_ROLE: "Pauser",
+ LEVEL_OPERATOR_ROLE: "Level Operator",
+ MANAGER_ROLE: "Manager",
+ WHITELISTER_ROLE: "Whitelister",
+ grant_role: "Grant Role",
+ revoke_role: "Revoke Role",
+ granting: "Granting...",
+ revoking: "Revoking...",
+ grant_success_title: "Role Granted Successfully",
+ grant_success_description: "The role has been granted to the user.",
+ revoke_success_title: "Role Revoked Successfully",
+ revoke_success_description: "The role has been revoked from the user.",
+ error_title: "Role Operation Failed",
+ error_description: "There was an error with the role operation: {error:string}",
+ invalid_address: "Please enter a valid address",
+ role_required: "Please select a role",
+ address_required: "User address is required",
+ },
+ },
} satisfies BaseTranslation;
export default en;
diff --git a/apps/frontend/src/i18n/i18n-react.tsx b/apps/frontend/src/i18n/i18n-react.tsx
index 75b2fd62..6c5bce09 100644
--- a/apps/frontend/src/i18n/i18n-react.tsx
+++ b/apps/frontend/src/i18n/i18n-react.tsx
@@ -1,21 +1,16 @@
// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
/* eslint-disable */
-import { useContext } from "react";
-import { initI18nReact } from "typesafe-i18n/react";
-import type { I18nContextType } from "typesafe-i18n/react";
-import type { Formatters, Locales, TranslationFunctions, Translations } from "./i18n-types.js";
-import { loadedFormatters, loadedLocales } from "./i18n-util.js";
+import { useContext } from 'react'
+import { initI18nReact } from 'typesafe-i18n/react'
+import type { I18nContextType } from 'typesafe-i18n/react'
+import type { Formatters, Locales, TranslationFunctions, Translations } from './i18n-types.js'
+import { loadedFormatters, loadedLocales } from './i18n-util.js'
-const { component: TypesafeI18n, context: I18nContext } = initI18nReact<
- Locales,
- Translations,
- TranslationFunctions,
- Formatters
->(loadedLocales, loadedFormatters);
+const { component: TypesafeI18n, context: I18nContext } = initI18nReact(loadedLocales, loadedFormatters)
-const useI18nContext = (): I18nContextType => useContext(I18nContext);
+const useI18nContext = (): I18nContextType => useContext(I18nContext)
-export { I18nContext, useI18nContext };
+export { I18nContext, useI18nContext }
-export default TypesafeI18n;
+export default TypesafeI18n
diff --git a/apps/frontend/src/i18n/i18n-types.ts b/apps/frontend/src/i18n/i18n-types.ts
index 4beffb80..909040c0 100644
--- a/apps/frontend/src/i18n/i18n-types.ts
+++ b/apps/frontend/src/i18n/i18n-types.ts
@@ -1,2737 +1,4362 @@
// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
/* eslint-disable */
-import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from "typesafe-i18n";
+import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n'
-export type BaseTranslation = BaseTranslationType;
-export type BaseLocale = "en";
+export type BaseTranslation = BaseTranslationType
+export type BaseLocale = 'en'
-export type Locales = "en";
+export type Locales =
+ | 'en'
-export type Translation = RootTranslation;
+export type Translation = RootTranslation
-export type Translations = RootTranslation;
+export type Translations = RootTranslation
type RootTranslation = {
- /**
- * Left
- */
- left: string;
- /**
- * by
- */
- by: string;
- /**
- * Not published
- */
- not_published: string;
- /**
- * Homepage
- */
- homepage: string;
- /**
- * Back
- */
- back: string;
- /**
- * Start
- */
- start: string;
- /**
- * End
- */
- end: string;
- /**
- * Edit
- */
- edit: string;
- /**
- * On
- */
- on: string;
- /**
- * All
- */
- all: string;
- /**
- * Finished
- */
- finished: string;
- /**
- * Executed
- */
- executed: string;
- /**
- * Show more
- */
- show_more: string;
- /**
- * Exit
- */
- exit: string;
- /**
- * Next
- */
- next: string;
- /**
- * Learn more
- */
- learn_more: string;
- /**
- * See details
- */
- see_details: string;
- /**
- * Select
- */
- select: string;
- /**
- * Select between
- */
- select_between: string;
- /**
- * and
- */
- and: string;
- /**
- * one
- */
- one: string;
- /**
- * Voting
- */
- voting: string;
- /**
- * Voters
- */
- voters: string;
- /**
- * Most voted
- */
- most_voted: string;
- /**
- * Votes
- */
- votes: string;
- /**
- * Delete
- */
- delete: string;
- /**
- * Cancel
- */
- cancel: string;
- /**
- * %
- */
- percentage: string;
- /**
- * Submit
- */
- submit: string;
- /**
- * Submit Vote
- */
- submit_vote: string;
- /**
- * Voting power
- */
- voting_power: string;
- /**
- * Your voting power
- */
- your_voting_power: string;
- /**
- * Voted
- */
- voted: string;
- /**
- * Vote
- */
- vote: string;
- /**
- * Wallet
- */
- wallet: string;
- /**
- * Block #
- */
- block: string;
- /**
- * Node
- */
- node: string;
- /**
- * Go back
- */
- go_back: string;
- /**
- * Optional
- */
- optional: string;
- /**
- * Buy a Node
- */
- buy_a_node: string;
- /**
- * {current}/{max}
- * @param {number} current
- * @param {number} max
- */
- filed_length: RequiredParams<"current" | "max">;
- /**
- * Upload
- */
- upload: string;
- /**
- * Copied to clipboard
- */
- copied_to_clipboard: string;
- /**
+ /**
+ * Left
+ */
+ left: string
+ /**
+ * by
+ */
+ by: string
+ /**
+ * Not published
+ */
+ not_published: string
+ /**
+ * Homepage
+ */
+ homepage: string
+ /**
+ * Back
+ */
+ back: string
+ /**
+ * Start
+ */
+ start: string
+ /**
+ * End
+ */
+ end: string
+ /**
+ * Edit
+ */
+ edit: string
+ /**
+ * On
+ */
+ on: string
+ /**
+ * All
+ */
+ all: string
+ /**
+ * Finished
+ */
+ finished: string
+ /**
+ * Executed
+ */
+ executed: string
+ /**
+ * Show more
+ */
+ show_more: string
+ /**
+ * Exit
+ */
+ exit: string
+ /**
+ * Next
+ */
+ next: string
+ /**
+ * Learn more
+ */
+ learn_more: string
+ /**
+ * See details
+ */
+ see_details: string
+ /**
+ * Select
+ */
+ select: string
+ /**
+ * Select between
+ */
+ select_between: string
+ /**
+ * and
+ */
+ and: string
+ /**
+ * one
+ */
+ one: string
+ /**
+ * Voting
+ */
+ voting: string
+ /**
+ * Voters
+ */
+ voters: string
+ /**
+ * Most voted
+ */
+ most_voted: string
+ /**
+ * Votes
+ */
+ votes: string
+ /**
+ * Delete
+ */
+ 'delete': string
+ /**
+ * Cancel
+ */
+ cancel: string
+ /**
+ * %
+ */
+ percentage: string
+ /**
+ * Submit
+ */
+ submit: string
+ /**
+ * Submit Vote
+ */
+ submit_vote: string
+ /**
+ * Voting power
+ */
+ voting_power: string
+ /**
+ * Your voting power
+ */
+ your_voting_power: string
+ /**
+ * Voted
+ */
+ voted: string
+ /**
+ * Vote
+ */
+ vote: string
+ /**
+ * Wallet
+ */
+ wallet: string
+ /**
+ * Block #
+ */
+ block: string
+ /**
+ * Node
+ */
+ node: string
+ /**
+ * Go back
+ */
+ go_back: string
+ /**
+ * Optional
+ */
+ optional: string
+ /**
+ * Buy a Node
+ */
+ buy_a_node: string
+ /**
+ * {current}/{max}
+ * @param {number} current
+ * @param {number} max
+ */
+ filed_length: RequiredParams<'current' | 'max'>
+ /**
+ * Upload
+ */
+ upload: string
+ /**
+ * Copied to clipboard
+ */
+ copied_to_clipboard: string
+ /**
* Image size should be 1280x512px (ratio 3:1).
JPG, PNG or SVG of maximum of {size}MB.
* @param {number} size
*/
- file_upload_description: RequiredParams<"size">;
- /**
- * Select date
- */
- select_date: string;
- /**
- * Select time
- */
- select_time: string;
- /**
- * Maximum
- */
- maximum: string;
- /**
- * Minimum
- */
- minimum: string;
- /**
- * Option {index}
- * @param {number} index
- */
- number_option: RequiredParams<"index">;
- /**
- * Continue
- */
- continue: string;
- /**
- * Results
- */
- results: string;
- /**
- * Description
- */
- description: string;
- /**
- * Preview
- */
- preview: string;
- /**
- * Close
- */
- close: string;
- /**
- * Confirm
- */
- confirm: string;
- /**
- * Try Again
- */
- try_again: string;
- /**
- * Read full description
- */
- read_full_description: string;
- /**
- * Disconnect
- */
- disconnect: string;
- /**
- * Connect Wallet
- */
- connect_wallet: string;
- /**
- * Connect your wallet to vote
- */
- connect_wallet_to_vote: string;
- /**
- * Comment
- */
- comment: string;
- /**
- * Add a comment to your vote...
- */
- comment_placeholder: string;
- /**
- * Migrate
- */
- migrate: string;
- /**
- * StarGate
- */
- stargate: string;
- /**
- * How voting power is obtained
- */
- learn_how_voting_power: string;
- /**
- * Join the discussion on Discourse
- */
- discuss_on_discourse: string;
- datepicker: {
- /**
- * Select date
- */
- select_date: string;
- /**
- * Previous month
- */
- previous_month: string;
- /**
- * Next month
- */
- next_month: string;
- /**
- * Today
- */
- today: string;
- weekdays: {
- /**
- * Mon
- */
- mon: string;
- /**
- * Tue
- */
- tue: string;
- /**
- * Wed
- */
- wed: string;
- /**
- * Thu
- */
- thu: string;
- /**
- * Fri
- */
- fri: string;
- /**
- * Sat
- */
- sat: string;
- /**
- * Sun
- */
- sun: string;
- };
- months: {
- /**
- * January
- */
- january: string;
- /**
- * February
- */
- february: string;
- /**
- * March
- */
- march: string;
- /**
- * April
- */
- april: string;
- /**
- * May
- */
- may: string;
- /**
- * June
- */
- june: string;
- /**
- * July
- */
- july: string;
- /**
- * August
- */
- august: string;
- /**
- * September
- */
- september: string;
- /**
- * October
- */
- october: string;
- /**
- * November
- */
- november: string;
- /**
- * December
- */
- december: string;
- };
- };
- timepicker: {
- /**
- * Select time
- */
- select_time: string;
- /**
- * Select time (UTC)
- */
- select_time_24h: string;
- /**
- * Hours
- */
- hours: string;
- /**
- * Minutes
- */
- minutes: string;
- /**
- * All times are in UTC
- */
- utc_notice: string;
- };
- home: {
- /**
- * Home
- */
- title: string;
- /**
- * Go to proposals
- */
- go_to_proposals: string;
- };
- node_names: {
- /**
- * not defined
- */
- none: string;
- /**
- * Strength
- */
- strength: string;
- /**
- * Thunder
- */
- thunder: string;
- /**
- * Mjolnir
- */
- mjolnir: string;
- /**
- * VeThor X
- */
- vethorx: string;
- /**
- * Strength X
- */
- strengthx: string;
- /**
- * Thunder X
- */
- thunderx: string;
- /**
- * Mjolnir X
- */
- mjolnirx: string;
- /**
- * Flash
- */
- flash: string;
- /**
- * Lightning
- */
- lightning: string;
- /**
- * Dawn
- */
- dawn: string;
- /**
- * Validator
- */
- validator: string;
- /**
- * Validator (inactive)
- */
- inactive_validator: string;
- };
- field_errors: {
- /**
- * Required
- */
- required: string;
- /**
- * Invalid format
- */
- invalid_format: string;
- /**
- * The end date must be after the start date
- */
- end_before_start: string;
- /**
- * The end date must be in the future
- */
- end_before_today: string;
- /**
- * The start date must be in the future
- */
- start_after_today: string;
- /**
- * The end date must be within {days} days of the start date
- * @param {string} days
- */
- end_after_max_duration: RequiredParams<"days">;
- /**
- * No voters found matching your search criteria.
- */
- failed_load_voters: string;
- descriptions_errors: {
- /**
- * Please replace placeholder text with your own content before submitting the proposal.
- */
- placeholders_not_replaced: string;
- /**
- * Description cannot be empty. Please provide content for your proposal.
- */
- empty_description: string;
- };
- /**
- * The Discourse topic does not exist or is not accessible
- */
- discourse_topic_not_exist: string;
- };
- voting_list: {
- /**
- * Voting options:
- */
- voting_options: string;
- /**
- * Select an option to vote:
- */
- option_to_vote: string;
- /**
- * Voting has not started yet
- */
- voting_has_not_started_yet: string;
- /**
- * Please connect your wallet
- */
- please_connect_your_wallet: string;
- /**
- * You have already voted
- */
- you_have_already_voted: string;
- /**
- * You don't have enough voting power
- */
- you_dont_have_enough_voting_power: string;
- };
- proposal: {
- /**
- * Proposal
- */
- title: string;
- /**
- * Proposed by
- */
- proposed_by: string;
- /**
- * Voting calendar
- */
- voting_calendar: string;
- /**
- * Confirm in your wallet...
- */
- confirm_in_your_wallet: string;
- /**
- * Who can vote
- */
- who_can_vote: string;
- /**
- * VeChain Foundation
- */
- vechain_foundation: string;
- /**
- * Node holders with voting power will be able to vote on this proposal.
- */
- node_holders: string;
- /**
- * Voting will start {date}
- * @param {string} date
- */
- voting_will_start: RequiredParams<"date">;
- /**
- * See your vote details
- */
- see_your_vote: string;
- /**
- * See all ({voters}) voters
- * @param {number} voters
- */
- see_all_voters: RequiredParams<"voters">;
- /**
- * See first voter
- */
- see_first_voter: string;
- /**
- * Mark as executed
- */
- mark_as_executed: string;
- /**
- * Buy another node to increase your voting power on future proposals.
- */
- buy_another_node: string;
- /**
- * Voting is only possible for Node holders. Buy a node to vote on future proposals or increase your voting power.
- */
- buy_a_node: string;
- vote_success: {
- /**
- * Vote submitted!
- */
- title: string;
- /**
- * Your vote was submitted successfully.
- */
- description: string;
- };
- cancel_proposal: {
- /**
- * Cancel proposal
- */
- title: string;
- /**
- * Canceling the proposal means it the voting will not take place and the proposal will not have no results.
- */
- description: string;
- /**
- * Reason
- */
- reason: string;
- /**
- * Write the reason for cancellation...
- */
- reason_placeholder: string;
- /**
- * Proposal canceled successfully
- */
- success_title: string;
- /**
- * The proposal has been canceled successfully. Voting will not take place.
- */
- success_description: string;
- };
- execute_proposal: {
- /**
- * Mark as Executed
- */
- title: string;
- /**
- * If the actions of the proposal have already been executed, you can mark this approved proposal as executed for the voters to know.
- */
- description: string;
- /**
- * Execution / Transaction details
- */
- label: string;
- /**
- * Insert link with the proof of the execution
- */
- link_placeholder: string;
- };
- delete_proposal: {
- /**
- * Delete proposal
- */
- title: string;
- /**
- * If you delete this draft, all the information of the proposal will not be possible to recover anymore.
- */
- description: string;
- /**
- * Are you sure you want to delete it?
- */
- confirmation: string;
- /**
- * No, go back
- */
- no_go_back: string;
- /**
- * Yes, Delete
- */
- yes_delete: string;
- };
- info_box: {
- info: {
- /**
- * Minimum participation
- */
- title: string;
- /**
- * A minimum of {quorum}% participation must be reached to validate the voting of the proposal and get approval.
- * @param {number} quorum
- */
- description: RequiredParams<"quorum">;
- };
- approved: {
- /**
- * Minimum participation reached
- */
- title: string;
- /**
- * The voting participation reached the minimum required of {quorum}% to get approval.
- * @param {number} quorum
- */
- description: RequiredParams<"quorum">;
- };
- executed: {
- /**
- * Proposal Approved and Executed
- */
- title: string;
- /**
- * The voting approved the proposal and the actions have been executed.
- */
- description: string;
- };
- "min-not-reached": {
- /**
- * Minimum participation not reached
- */
- title: string;
- /**
- * The voting participation didn’t reached the minimum required of {quorum}% to get approval.
- * @param {number} quorum
- */
- description: RequiredParams<"quorum">;
- };
- rejected: {
- /**
- * Proposal Rejected
- */
- title: string;
- /**
- * The proposal didn’t get enough votes in favor to get approval.
- */
- description: string;
- };
- canceled: {
- /**
- * Proposal Canceled
- */
- title: string;
- /**
- * The proposal was canceled by VeChain or the proposer by the following reason:
- */
- description: string;
- };
- };
- voting_power: {
- /**
- * Get more voting power
- */
- get_more_voting_power: string;
- /**
- * Get voting power
- */
- get_voting_power: string;
- /**
- * Voting power
- */
- title: string;
- /**
- * Your voting power was calculated at the time of the snapshot {snapshot}.
- * @param {string} snapshot
- */
- calculation: RequiredParams<"snapshot">;
- /**
- * Total voting power
- */
- total_voting_power: string;
- warnings: {
- /**
- * The connected wallet has no voting power
- */
- zero_voting_power: string;
- /**
- * You have legacy nodes that haven’t been migrated yet. Migrate to get more voting power.
- */
- legacy_node: string;
- delegated: {
- /**
- * Your voting power is delegated
- */
- title: string;
- /**
- * Your voting power is delegated to another node
- */
- description: string;
- };
- };
- };
- voters_table: {
- filters: {
- /**
- * Search by address...
- */
- search_by_address: string;
- /**
- * Voting options
- */
- voting_options: string;
- /**
- * Node
- */
- node: string;
- /**
- * Sort by
- */
- sort_by: string;
- };
- header: {
- /**
- * Date
- */
- date: string;
- /**
- * Address
- */
- address: string;
- /**
- * Node
- */
- node: string;
- /**
- * Node ID
- */
- node_id: string;
- /**
- * Power
- */
- voting_power: string;
- /**
- * Option
- */
- voted_option: string;
- /**
- * Transaction ID
- */
- transaction_id: string;
- };
- };
- create: {
- /**
- * Previewing proposal
- */
- previewing: string;
- /**
- * Create Proposal
- */
- title: string;
- /**
- * {current} of {total}
- * @param {number} current
- * @param {number} total
- */
- steps: RequiredParams<"current" | "total">;
- /**
- * Add the main details and setup the calendar
- */
- voting_details_desc: string;
- /**
- * Define the voting setup details
- */
- voting_setup_desc: string;
- /**
- * Review all the details before publishing
- */
- voting_summary_desc: string;
- /**
- * Add new option
- */
- add_new_option: string;
- exit_proposal: {
- /**
- * Exit proposal creation
- */
- title: string;
- /**
- * By exiting you lose all the information written or you can save this proposal as a draft and finish later?
- */
- description: string;
- /**
- * You will lose all entered information if you exit now. Saving as a draft is only available on the final step of the form.
- */
- description_last_step: string;
- /**
- * Be aware that a draft proposal already exists. Saving now will overwrite your previous draft.
- */
- description_draft_exist: string;
- /**
- * Exit proposal
- */
- exit_button: string;
- /**
- * Save draft
- */
- save_button: string;
- };
- draft_saved: {
- /**
- * Draft saved!
- */
- title: string;
- /**
- * The proposal draft has been saved successfully and can now be continued later.
- */
- description: string;
- };
- details_form: {
- /**
- * Title
- */
- title: string;
- /**
- * What's the proposal title?
- */
- title_placeholder: string;
- /**
- * Description
- */
- description: string;
- /**
- * Add a description...
- */
- description_placeholder: string;
- /**
- * Header image
- */
- header_image: string;
- /**
- * Discourse url
- */
- discourse_url: string;
- /**
- * Discourse Topic
- */
- discourse_topic: string;
- /**
- * your-topic-name
- */
- discourse_topic_placeholder: string;
- /**
- * Enter the topic name from your VeChain Discourse discussion
- */
- discourse_topic_help: string;
- /**
- * Voting calendar
- */
- voting_calendar: string;
- };
- setup_form: {
- /**
- * Voting type
- */
- voting_type: string;
- /**
- * Select the type
- */
- voting_type_subtitle: string;
- /**
- * Voting Question
- */
- voting_question: string;
- /**
- * This question should provide exact context to the voting options:
- */
- voting_question_subtitle: string;
- /**
- * Write the question...
- */
- voting_question_placeholder: string;
- /**
- * Voting limit
- */
- voting_limit: string;
- /**
- * Define the minimum and maximum amount of options allowed per voter:
- */
- voting_limit_subtitle: string;
- /**
- * Voting options
- */
- voting_options: string;
- /**
- * The “single choice” voting type only allows the voter to select:
- */
- voting_choice_subtitle: string;
- /**
- * Add between 2 and 30 options to vote:
- */
- voting_options_subtitle: string;
- /**
- * Add new option
- */
- add_new_option: string;
- /**
- * Write the voting option...
- */
- voting_option_placeholder: string;
- };
- summary_form: {
- main_details: {
- /**
- * Main details
- */
- title: string;
- /**
- * Calendar
- */
- calendar: string;
- };
- voting_setup: {
- /**
- * Voting setup
- */
- title: string;
- /**
- * Question
- */
- question: string;
- /**
- * Type
- */
- type: string;
- /**
- * Minimum {min} options - Maximum {limit} options
- * @param {number} limit
- * @param {number} min
- */
- limit: RequiredParams<"limit" | "min">;
- types: {
- /**
- * Single choice - For / Against / Abstain
- */
- SINGLE_CHOICE: string;
- };
- };
- /**
- * Publish Proposal
- */
- publish_proposal: string;
- /**
- * Please note that once the campaign is published, it can't be edited anymore.
- */
- publish_description: string;
- /**
- * Are you sure you want to publish this proposal?
- */
- publish_sub_description: string;
- /**
- * Publishing failed
- */
- publish_failed: string;
- /**
- * The publishing of the proposal couldn’t be completed. Please try again.
- */
- publish_failed_description: string;
- /**
- * Proposal published
- */
- publish_success: string;
- /**
- * The proposal has been successfully publish and can now be seen publicly.
- */
- publish_success_description: string;
- };
- };
- /**
- * Go to StarGate
- */
- go_to_stargate: string;
- /**
- * Proposal not found
- */
- proposal_not_found: string;
- /**
- * The proposal you're looking for doesn't exist or may have been removed. It's possible the URL is incorrect or the proposal has been deleted.
- */
- proposal_not_found_description: string;
- /**
- * Back to Proposals
- */
- back_to_proposals: string;
- /**
- * Try Again
- */
- try_again: string;
- /**
- * Starts in
- */
- starts_in: string;
- /**
- * Ends in
- */
- ends_in: string;
- /**
- * Timeline
- */
- timeline: string;
- /**
- * Created
- */
- timeline_created: string;
- /**
- * Proposal Canceled
- */
- proposal_canceled: string;
- /**
- * The proposal was canceled by VeChain or the proposer by the following reason:
- */
- proposal_canceled_description: string;
- /**
- * No reason provided
- */
- no_reason_provided: string;
- /**
- * Unknown error
- */
- unknown_error: string;
- /**
- * Failed to execute proposal
- */
- failed_to_execute_proposal: string;
- /**
- * Proposal Approved and Executed
- */
- proposal_approved_and_executed: string;
- /**
- * The voting participation reached the minimum quorum to get approval.
- */
- proposal_approved: string;
- /**
- * The voting approved the proposal and the actions have been executed.
- */
- the_voting_approved_the_proposal_and_the_actions_have_been_executed: string;
- /**
- * See details
- */
- see_details: string;
- /**
- * Proposal Rejected
- */
- proposal_rejected: string;
- /**
- * The proposal didn't get enough votes in favor to get approval.
- */
- the_proposal_didnt_get_enough_votes_in_favor_to_get_approval: string;
- /**
- * Minimum participant not met
- */
- minimum_quorum_not_reached: string;
- /**
- * Quorum of {quorum} voting power not reached.
- * @param {string} quorum
- */
- quorum_not_reached: RequiredParams<"quorum">;
- /**
- * Quorum of {quorum} voting power not reached yet.
- * @param {string} quorum
- */
- quorum_not_reached_yet: RequiredParams<"quorum">;
- /**
- * Quorum reached.
- */
- quorum_reached: string;
- /**
- * Vote submission failed
- */
- vote_submission_failed: string;
- /**
- * Vote submitted successfully!
- */
- vote_submitted_successfully: string;
- /**
- * Submit your vote
- */
- submit_your_vote: string;
- /**
- * Your vote cannot be changed once submitted.
- */
- vote_cannot_be_changed: string;
- /**
- * Waiting wallet confirmation...
- */
- waiting_wallet_confirmation: string;
- /**
- * Confirm vote
- */
- confirm_vote: string;
- /**
- * You voted
- */
- you_voted: string;
- };
- proposals: {
- /**
- * Proposals
- */
- title: string;
- /**
- * Create Proposal
- */
- create: string;
- /**
- * Search proposals...
- */
- search_placeholder: string;
- /**
- * No proposals found
- */
- no_proposals: string;
- /**
- * {current} of {total} proposals
- * @param {number} current
- * @param {number} total
- */
- pagination: RequiredParams<"current" | "total">;
- };
- statuses: {
- /**
- * Voted
- */
- voted: string;
- };
- badge: {
- /**
- * Draft
- */
- draft: string;
- /**
- * Upcoming
- */
- upcoming: string;
- /**
- * Voting now
- */
- voting: string;
- /**
- * Approved
- */
- approved: string;
- /**
- * Executed
- */
- executed: string;
- /**
- * Canceled
- */
- canceled: string;
- /**
- * Rejected
- */
- rejected: string;
- /**
- * Quorum not reached
- */
- "min-not-reached": string;
- };
- filters: {
- /**
- * Filters
- */
- title: string;
- /**
- * Apply
- */
- apply: string;
- /**
- * Reset
- */
- reset: string;
- /**
- * Select all
- */
- select_all: string;
- /**
- * Deselect all
- */
- deselect_all: string;
- sort: {
- /**
- * Newest
- */
- newest: string;
- /**
- * Oldest
- */
- oldest: string;
- /**
- * Most participant
- */
- most_participant: string;
- /**
- * Least participant
- */
- least_participant: string;
- };
- };
- header: {
- /**
- * VeChainThor Voting Platform
- */
- title: string;
- /**
- * Vote to shape the future of VeChainThor
- */
- description: string;
- /**
- * How to vote
- */
- how_to_vote: string;
- /**
- * Get voting power
- */
- how_to_get_voting_power: string;
- };
- stargate_warning: {
- /**
- * StarGate Node Migration Required
- */
- title: string;
- /**
- * You have 1 or more non-migrated nodes. Please migrate them as soon as possible to continue voting.
- */
- description: string;
- /**
- * https://app.stargate.vechain.org/
- */
- migration_link: string;
- /**
- * If a proposal has already started, you will not be able to vote on it even after migration. Only future proposals will be available for voting.
- */
- ongoing_proposal_warning: string;
- /**
- * If you want to continue anyway, write this text: agree-with-this
- */
- confirmation_instruction: string;
- /**
- * Please type exactly 'agree-with-this' to continue
- */
- confirmation_error: string;
- };
- footer: {
- /**
- * v1.0.0
- */
- version: string;
- /**
- * All Rights Reserved © Vechain Foundation San Marino S.r.l.
- */
- all_right: string;
- legal: {
- /**
- * Legal
- */
- title: string;
- /**
- * Terms of Service
- */
- terms_of_service: string;
- /**
- * Privacy Policy
- */
- privacy_policy: string;
- /**
- * Cookies Policy
- */
- cookies_policy: string;
- };
- resources: {
- /**
- * Resources
- */
- title: string;
- /**
- * Docs
- */
- docs: string;
- /**
- * StarGate
- */
- stargate: string;
- /**
- * Support
- */
- support: string;
- /**
- * Governance Charter
- */
- governance_charter: string;
- };
- };
-};
+ file_upload_description: RequiredParams<'size'>
+ /**
+ * Select date
+ */
+ select_date: string
+ /**
+ * Select time
+ */
+ select_time: string
+ /**
+ * Maximum
+ */
+ maximum: string
+ /**
+ * Minimum
+ */
+ minimum: string
+ /**
+ * Option {index}
+ * @param {number} index
+ */
+ number_option: RequiredParams<'index'>
+ /**
+ * Continue
+ */
+ 'continue': string
+ /**
+ * Results
+ */
+ results: string
+ /**
+ * Description
+ */
+ description: string
+ /**
+ * Preview
+ */
+ preview: string
+ /**
+ * Close
+ */
+ close: string
+ /**
+ * Confirm
+ */
+ confirm: string
+ /**
+ * Try Again
+ */
+ try_again: string
+ /**
+ * Read full description
+ */
+ read_full_description: string
+ /**
+ * Disconnect
+ */
+ disconnect: string
+ /**
+ * Connect Wallet
+ */
+ connect_wallet: string
+ /**
+ * Connect your wallet to vote
+ */
+ connect_wallet_to_vote: string
+ /**
+ * Comment
+ */
+ comment: string
+ /**
+ * Add a comment to your vote...
+ */
+ comment_placeholder: string
+ /**
+ * Migrate
+ */
+ migrate: string
+ /**
+ * StarGate
+ */
+ stargate: string
+ /**
+ * How voting power is obtained
+ */
+ learn_how_voting_power: string
+ /**
+ * Join the discussion on Discourse
+ */
+ discuss_on_discourse: string
+ common: {
+ time: {
+ /**
+ * {count} {count|{1: second, *: seconds}}
+ * @param {number | '1' | string} count
+ */
+ seconds: RequiredParams<'count' | `count|{1:${string}, *:${string}}`>
+ /**
+ * {count} {count|{1: minute, *: minutes}}
+ * @param {number | '1' | string} count
+ */
+ minutes: RequiredParams<'count' | `count|{1:${string}, *:${string}}`>
+ /**
+ * {count} {count|{1: hour, *: hours}}
+ * @param {number | '1' | string} count
+ */
+ hours: RequiredParams<'count' | `count|{1:${string}, *:${string}}`>
+ /**
+ * {count} {count|{1: day, *: days}}
+ * @param {number | '1' | string} count
+ */
+ days: RequiredParams<'count' | `count|{1:${string}, *:${string}}`>
+ }
+ }
+ datepicker: {
+ /**
+ * Select date
+ */
+ select_date: string
+ /**
+ * Previous month
+ */
+ previous_month: string
+ /**
+ * Next month
+ */
+ next_month: string
+ /**
+ * Today
+ */
+ today: string
+ weekdays: {
+ /**
+ * Mon
+ */
+ mon: string
+ /**
+ * Tue
+ */
+ tue: string
+ /**
+ * Wed
+ */
+ wed: string
+ /**
+ * Thu
+ */
+ thu: string
+ /**
+ * Fri
+ */
+ fri: string
+ /**
+ * Sat
+ */
+ sat: string
+ /**
+ * Sun
+ */
+ sun: string
+ }
+ months: {
+ /**
+ * January
+ */
+ january: string
+ /**
+ * February
+ */
+ february: string
+ /**
+ * March
+ */
+ march: string
+ /**
+ * April
+ */
+ april: string
+ /**
+ * May
+ */
+ may: string
+ /**
+ * June
+ */
+ june: string
+ /**
+ * July
+ */
+ july: string
+ /**
+ * August
+ */
+ august: string
+ /**
+ * September
+ */
+ september: string
+ /**
+ * October
+ */
+ october: string
+ /**
+ * November
+ */
+ november: string
+ /**
+ * December
+ */
+ december: string
+ }
+ }
+ timepicker: {
+ /**
+ * Select time
+ */
+ select_time: string
+ /**
+ * Select time (UTC)
+ */
+ select_time_24h: string
+ /**
+ * Hours
+ */
+ hours: string
+ /**
+ * Minutes
+ */
+ minutes: string
+ /**
+ * All times are in UTC
+ */
+ utc_notice: string
+ }
+ home: {
+ /**
+ * Home
+ */
+ title: string
+ /**
+ * Go to proposals
+ */
+ go_to_proposals: string
+ }
+ node_names: {
+ /**
+ * not defined
+ */
+ none: string
+ /**
+ * Strength
+ */
+ strength: string
+ /**
+ * Thunder
+ */
+ thunder: string
+ /**
+ * Mjolnir
+ */
+ mjolnir: string
+ /**
+ * VeThor X
+ */
+ vethorx: string
+ /**
+ * Strength X
+ */
+ strengthx: string
+ /**
+ * Thunder X
+ */
+ thunderx: string
+ /**
+ * Mjolnir X
+ */
+ mjolnirx: string
+ /**
+ * Flash
+ */
+ flash: string
+ /**
+ * Lightning
+ */
+ lightning: string
+ /**
+ * Dawn
+ */
+ dawn: string
+ /**
+ * Validator
+ */
+ validator: string
+ /**
+ * Validator (inactive)
+ */
+ inactive_validator: string
+ }
+ field_errors: {
+ /**
+ * Required
+ */
+ required: string
+ /**
+ * Invalid format
+ */
+ invalid_format: string
+ /**
+ * Please enter a valid address
+ */
+ invalid_address: string
+ /**
+ * The end date must be after the start date
+ */
+ end_before_start: string
+ /**
+ * The end date must be in the future
+ */
+ end_before_today: string
+ /**
+ * The start date must be in the future
+ */
+ start_after_today: string
+ /**
+ * The end date must be within {days} days of the start date
+ * @param {string} days
+ */
+ end_after_max_duration: RequiredParams<'days'>
+ /**
+ * No voters found matching your search criteria.
+ */
+ failed_load_voters: string
+ descriptions_errors: {
+ /**
+ * Please replace placeholder text with your own content before submitting the proposal.
+ */
+ placeholders_not_replaced: string
+ /**
+ * Description cannot be empty. Please provide content for your proposal.
+ */
+ empty_description: string
+ }
+ /**
+ * The Discourse topic does not exist or is not accessible
+ */
+ discourse_topic_not_exist: string
+ }
+ voting_list: {
+ /**
+ * Voting options:
+ */
+ voting_options: string
+ /**
+ * Select an option to vote:
+ */
+ option_to_vote: string
+ /**
+ * Voting has not started yet
+ */
+ voting_has_not_started_yet: string
+ /**
+ * Please connect your wallet
+ */
+ please_connect_your_wallet: string
+ /**
+ * You have already voted
+ */
+ you_have_already_voted: string
+ /**
+ * You don't have enough voting power
+ */
+ you_dont_have_enough_voting_power: string
+ }
+ proposal: {
+ /**
+ * Proposal
+ */
+ title: string
+ /**
+ * Proposed by
+ */
+ proposed_by: string
+ /**
+ * Voting calendar
+ */
+ voting_calendar: string
+ /**
+ * Confirm in your wallet...
+ */
+ confirm_in_your_wallet: string
+ /**
+ * Who can vote
+ */
+ who_can_vote: string
+ /**
+ * VeChain Foundation
+ */
+ vechain_foundation: string
+ /**
+ * Node holders with voting power will be able to vote on this proposal.
+ */
+ node_holders: string
+ /**
+ * Voting will start {date}
+ * @param {string} date
+ */
+ voting_will_start: RequiredParams<'date'>
+ /**
+ * See your vote details
+ */
+ see_your_vote: string
+ /**
+ * See all ({voters}) voters
+ * @param {number} voters
+ */
+ see_all_voters: RequiredParams<'voters'>
+ /**
+ * See first voter
+ */
+ see_first_voter: string
+ /**
+ * Mark as executed
+ */
+ mark_as_executed: string
+ /**
+ * Buy another node to increase your voting power on future proposals.
+ */
+ buy_another_node: string
+ /**
+ * Voting is only possible for Node holders. Buy a node to vote on future proposals or increase your voting power.
+ */
+ buy_a_node: string
+ vote_success: {
+ /**
+ * Vote submitted!
+ */
+ title: string
+ /**
+ * Your vote was submitted successfully.
+ */
+ description: string
+ }
+ cancel_proposal: {
+ /**
+ * Cancel proposal
+ */
+ title: string
+ /**
+ * Canceling the proposal means it the voting will not take place and the proposal will not have no results.
+ */
+ description: string
+ /**
+ * Reason
+ */
+ reason: string
+ /**
+ * Write the reason for cancellation...
+ */
+ reason_placeholder: string
+ /**
+ * Proposal canceled successfully
+ */
+ success_title: string
+ /**
+ * The proposal has been canceled successfully. Voting will not take place.
+ */
+ success_description: string
+ }
+ execute_proposal: {
+ /**
+ * Mark as Executed
+ */
+ title: string
+ /**
+ * If the actions of the proposal have already been executed, you can mark this approved proposal as executed for the voters to know.
+ */
+ description: string
+ /**
+ * Execution / Transaction details
+ */
+ label: string
+ /**
+ * Insert link with the proof of the execution
+ */
+ link_placeholder: string
+ }
+ delete_proposal: {
+ /**
+ * Delete proposal
+ */
+ title: string
+ /**
+ * If you delete this draft, all the information of the proposal will not be possible to recover anymore.
+ */
+ description: string
+ /**
+ * Are you sure you want to delete it?
+ */
+ confirmation: string
+ /**
+ * No, go back
+ */
+ no_go_back: string
+ /**
+ * Yes, Delete
+ */
+ yes_delete: string
+ }
+ info_box: {
+ info: {
+ /**
+ * Minimum participation
+ */
+ title: string
+ /**
+ * A minimum of {quorum}% participation must be reached to validate the voting of the proposal and get approval.
+ * @param {number} quorum
+ */
+ description: RequiredParams<'quorum'>
+ }
+ approved: {
+ /**
+ * Minimum participation reached
+ */
+ title: string
+ /**
+ * The voting participation reached the minimum required of {quorum}% to get approval.
+ * @param {number} quorum
+ */
+ description: RequiredParams<'quorum'>
+ }
+ executed: {
+ /**
+ * Proposal Approved and Executed
+ */
+ title: string
+ /**
+ * The voting approved the proposal and the actions have been executed.
+ */
+ description: string
+ }
+ 'min-not-reached': {
+ /**
+ * Minimum participation not reached
+ */
+ title: string
+ /**
+ * The voting participation didn’t reached the minimum required of {quorum}% to get approval.
+ * @param {number} quorum
+ */
+ description: RequiredParams<'quorum'>
+ }
+ rejected: {
+ /**
+ * Proposal Rejected
+ */
+ title: string
+ /**
+ * The proposal didn’t get enough votes in favor to get approval.
+ */
+ description: string
+ }
+ canceled: {
+ /**
+ * Proposal Canceled
+ */
+ title: string
+ /**
+ * The proposal was canceled by VeChain or the proposer by the following reason:
+ */
+ description: string
+ }
+ }
+ voting_power: {
+ /**
+ * Get more voting power
+ */
+ get_more_voting_power: string
+ /**
+ * Get voting power
+ */
+ get_voting_power: string
+ /**
+ * Voting power
+ */
+ title: string
+ /**
+ * Your voting power was calculated at the time of the snapshot {snapshot}.
+ * @param {string} snapshot
+ */
+ calculation: RequiredParams<'snapshot'>
+ /**
+ * Total voting power
+ */
+ total_voting_power: string
+ warnings: {
+ /**
+ * The connected wallet has no voting power
+ */
+ zero_voting_power: string
+ /**
+ * You have legacy nodes that haven’t been migrated yet. Migrate to get more voting power.
+ */
+ legacy_node: string
+ delegated: {
+ /**
+ * Your voting power is delegated
+ */
+ title: string
+ /**
+ * Your voting power is delegated to another node
+ */
+ description: string
+ }
+ }
+ }
+ voters_table: {
+ filters: {
+ /**
+ * Search by address...
+ */
+ search_by_address: string
+ /**
+ * Voting options
+ */
+ voting_options: string
+ /**
+ * Node
+ */
+ node: string
+ /**
+ * Sort by
+ */
+ sort_by: string
+ }
+ header: {
+ /**
+ * Date
+ */
+ date: string
+ /**
+ * Address
+ */
+ address: string
+ /**
+ * Node
+ */
+ node: string
+ /**
+ * Node ID
+ */
+ node_id: string
+ /**
+ * Power
+ */
+ voting_power: string
+ /**
+ * Option
+ */
+ voted_option: string
+ /**
+ * Transaction ID
+ */
+ transaction_id: string
+ }
+ }
+ create: {
+ /**
+ * Previewing proposal
+ */
+ previewing: string
+ /**
+ * Create Proposal
+ */
+ title: string
+ /**
+ * {current} of {total}
+ * @param {number} current
+ * @param {number} total
+ */
+ steps: RequiredParams<'current' | 'total'>
+ /**
+ * Add the main details and setup the calendar
+ */
+ voting_details_desc: string
+ /**
+ * Define the voting setup details
+ */
+ voting_setup_desc: string
+ /**
+ * Review all the details before publishing
+ */
+ voting_summary_desc: string
+ /**
+ * Add new option
+ */
+ add_new_option: string
+ exit_proposal: {
+ /**
+ * Exit proposal creation
+ */
+ title: string
+ /**
+ * By exiting you lose all the information written or you can save this proposal as a draft and finish later?
+ */
+ description: string
+ /**
+ * You will lose all entered information if you exit now. Saving as a draft is only available on the final step of the form.
+ */
+ description_last_step: string
+ /**
+ * Be aware that a draft proposal already exists. Saving now will overwrite your previous draft.
+ */
+ description_draft_exist: string
+ /**
+ * Exit proposal
+ */
+ exit_button: string
+ /**
+ * Save draft
+ */
+ save_button: string
+ }
+ draft_saved: {
+ /**
+ * Draft saved!
+ */
+ title: string
+ /**
+ * The proposal draft has been saved successfully and can now be continued later.
+ */
+ description: string
+ }
+ details_form: {
+ /**
+ * Title
+ */
+ title: string
+ /**
+ * What's the proposal title?
+ */
+ title_placeholder: string
+ /**
+ * Description
+ */
+ description: string
+ /**
+ * Add a description...
+ */
+ description_placeholder: string
+ /**
+ * Header image
+ */
+ header_image: string
+ /**
+ * Discourse url
+ */
+ discourse_url: string
+ /**
+ * Discourse Topic
+ */
+ discourse_topic: string
+ /**
+ * your-topic-name
+ */
+ discourse_topic_placeholder: string
+ /**
+ * Enter the topic name from your VeChain Discourse discussion
+ */
+ discourse_topic_help: string
+ /**
+ * Voting calendar
+ */
+ voting_calendar: string
+ }
+ setup_form: {
+ /**
+ * Voting type
+ */
+ voting_type: string
+ /**
+ * Select the type
+ */
+ voting_type_subtitle: string
+ /**
+ * Voting Question
+ */
+ voting_question: string
+ /**
+ * This question should provide exact context to the voting options:
+ */
+ voting_question_subtitle: string
+ /**
+ * Write the question...
+ */
+ voting_question_placeholder: string
+ /**
+ * Voting limit
+ */
+ voting_limit: string
+ /**
+ * Define the minimum and maximum amount of options allowed per voter:
+ */
+ voting_limit_subtitle: string
+ /**
+ * Voting options
+ */
+ voting_options: string
+ /**
+ * The “single choice” voting type only allows the voter to select:
+ */
+ voting_choice_subtitle: string
+ /**
+ * Add between 2 and 30 options to vote:
+ */
+ voting_options_subtitle: string
+ /**
+ * Add new option
+ */
+ add_new_option: string
+ /**
+ * Write the voting option...
+ */
+ voting_option_placeholder: string
+ }
+ summary_form: {
+ main_details: {
+ /**
+ * Main details
+ */
+ title: string
+ /**
+ * Calendar
+ */
+ calendar: string
+ }
+ voting_setup: {
+ /**
+ * Voting setup
+ */
+ title: string
+ /**
+ * Question
+ */
+ question: string
+ /**
+ * Type
+ */
+ type: string
+ /**
+ * Minimum {min} options - Maximum {limit} options
+ * @param {number} limit
+ * @param {number} min
+ */
+ limit: RequiredParams<'limit' | 'min'>
+ types: {
+ /**
+ * Single choice - For / Against / Abstain
+ */
+ SINGLE_CHOICE: string
+ }
+ }
+ /**
+ * Publish Proposal
+ */
+ publish_proposal: string
+ /**
+ * Please note that once the campaign is published, it can't be edited anymore.
+ */
+ publish_description: string
+ /**
+ * Are you sure you want to publish this proposal?
+ */
+ publish_sub_description: string
+ /**
+ * Publishing failed
+ */
+ publish_failed: string
+ /**
+ * The publishing of the proposal couldn’t be completed. Please try again.
+ */
+ publish_failed_description: string
+ /**
+ * Proposal published
+ */
+ publish_success: string
+ /**
+ * The proposal has been successfully publish and can now be seen publicly.
+ */
+ publish_success_description: string
+ }
+ }
+ /**
+ * Go to StarGate
+ */
+ go_to_stargate: string
+ /**
+ * Proposal not found
+ */
+ proposal_not_found: string
+ /**
+ * The proposal you're looking for doesn't exist or may have been removed. It's possible the URL is incorrect or the proposal has been deleted.
+ */
+ proposal_not_found_description: string
+ /**
+ * Back to Proposals
+ */
+ back_to_proposals: string
+ /**
+ * Try Again
+ */
+ try_again: string
+ /**
+ * Starts in
+ */
+ starts_in: string
+ /**
+ * Ends in
+ */
+ ends_in: string
+ /**
+ * Timeline
+ */
+ timeline: string
+ /**
+ * Created
+ */
+ timeline_created: string
+ /**
+ * Proposal Canceled
+ */
+ proposal_canceled: string
+ /**
+ * The proposal was canceled by VeChain or the proposer by the following reason:
+ */
+ proposal_canceled_description: string
+ /**
+ * No reason provided
+ */
+ no_reason_provided: string
+ /**
+ * Unknown error
+ */
+ unknown_error: string
+ /**
+ * Failed to execute proposal
+ */
+ failed_to_execute_proposal: string
+ /**
+ * Proposal Approved and Executed
+ */
+ proposal_approved_and_executed: string
+ /**
+ * The voting participation reached the minimum quorum to get approval.
+ */
+ proposal_approved: string
+ /**
+ * The voting approved the proposal and the actions have been executed.
+ */
+ the_voting_approved_the_proposal_and_the_actions_have_been_executed: string
+ /**
+ * See details
+ */
+ see_details: string
+ /**
+ * Proposal Rejected
+ */
+ proposal_rejected: string
+ /**
+ * The proposal didn't get enough votes in favor to get approval.
+ */
+ the_proposal_didnt_get_enough_votes_in_favor_to_get_approval: string
+ /**
+ * Minimum participant not met
+ */
+ minimum_quorum_not_reached: string
+ /**
+ * Quorum of {quorum} voting power not reached.
+ * @param {string} quorum
+ */
+ quorum_not_reached: RequiredParams<'quorum'>
+ /**
+ * Quorum of {quorum} voting power not reached yet.
+ * @param {string} quorum
+ */
+ quorum_not_reached_yet: RequiredParams<'quorum'>
+ /**
+ * Quorum reached.
+ */
+ quorum_reached: string
+ /**
+ * Vote submission failed
+ */
+ vote_submission_failed: string
+ /**
+ * Vote submitted successfully!
+ */
+ vote_submitted_successfully: string
+ /**
+ * Submit your vote
+ */
+ submit_your_vote: string
+ /**
+ * Your vote cannot be changed once submitted.
+ */
+ vote_cannot_be_changed: string
+ /**
+ * Waiting wallet confirmation...
+ */
+ waiting_wallet_confirmation: string
+ /**
+ * Confirm vote
+ */
+ confirm_vote: string
+ /**
+ * You voted
+ */
+ you_voted: string
+ }
+ proposals: {
+ /**
+ * Proposals
+ */
+ title: string
+ /**
+ * Create Proposal
+ */
+ create: string
+ /**
+ * Search proposals...
+ */
+ search_placeholder: string
+ /**
+ * No proposals found
+ */
+ no_proposals: string
+ /**
+ * {current} of {total} proposals
+ * @param {number} current
+ * @param {number} total
+ */
+ pagination: RequiredParams<'current' | 'total'>
+ }
+ statuses: {
+ /**
+ * Voted
+ */
+ voted: string
+ }
+ badge: {
+ /**
+ * Draft
+ */
+ draft: string
+ /**
+ * Upcoming
+ */
+ upcoming: string
+ /**
+ * Voting now
+ */
+ voting: string
+ /**
+ * Approved
+ */
+ approved: string
+ /**
+ * Executed
+ */
+ executed: string
+ /**
+ * Canceled
+ */
+ canceled: string
+ /**
+ * Rejected
+ */
+ rejected: string
+ /**
+ * Quorum not reached
+ */
+ 'min-not-reached': string
+ }
+ filters: {
+ /**
+ * Filters
+ */
+ title: string
+ /**
+ * Apply
+ */
+ apply: string
+ /**
+ * Reset
+ */
+ reset: string
+ /**
+ * Select all
+ */
+ select_all: string
+ /**
+ * Deselect all
+ */
+ deselect_all: string
+ sort: {
+ /**
+ * Newest
+ */
+ newest: string
+ /**
+ * Oldest
+ */
+ oldest: string
+ /**
+ * Most participant
+ */
+ most_participant: string
+ /**
+ * Least participant
+ */
+ least_participant: string
+ }
+ }
+ header: {
+ /**
+ * VeChainThor Voting Platform
+ */
+ title: string
+ /**
+ * Vote to shape the future of VeChainThor
+ */
+ description: string
+ /**
+ * How to vote
+ */
+ how_to_vote: string
+ /**
+ * Get voting power
+ */
+ how_to_get_voting_power: string
+ }
+ stargate_warning: {
+ /**
+ * StarGate Node Migration Required
+ */
+ title: string
+ /**
+ * You have 1 or more non-migrated nodes. Please migrate them as soon as possible to continue voting.
+ */
+ description: string
+ /**
+ * https://app.stargate.vechain.org/
+ */
+ migration_link: string
+ /**
+ * If a proposal has already started, you will not be able to vote on it even after migration. Only future proposals will be available for voting.
+ */
+ ongoing_proposal_warning: string
+ /**
+ * If you want to continue anyway, write this text: agree-with-this
+ */
+ confirmation_instruction: string
+ /**
+ * Please type exactly 'agree-with-this' to continue
+ */
+ confirmation_error: string
+ }
+ footer: {
+ /**
+ * v1.0.0
+ */
+ version: string
+ /**
+ * All Rights Reserved © Vechain Foundation San Marino S.r.l.
+ */
+ all_right: string
+ legal: {
+ /**
+ * Legal
+ */
+ title: string
+ /**
+ * Terms of Service
+ */
+ terms_of_service: string
+ /**
+ * Privacy Policy
+ */
+ privacy_policy: string
+ /**
+ * Cookies Policy
+ */
+ cookies_policy: string
+ }
+ resources: {
+ /**
+ * Resources
+ */
+ title: string
+ /**
+ * Docs
+ */
+ docs: string
+ /**
+ * StarGate
+ */
+ stargate: string
+ /**
+ * Support
+ */
+ support: string
+ /**
+ * Governance Charter
+ */
+ governance_charter: string
+ }
+ }
+ admin: {
+ /**
+ * Admin Dashboard
+ */
+ title: string
+ tabs: {
+ /**
+ * Contracts
+ */
+ contracts: string
+ /**
+ * Utils
+ */
+ utils: string
+ /**
+ * Users
+ */
+ users: string
+ /**
+ * Governance Settings
+ */
+ governance_settings: string
+ /**
+ * Voting Power Query
+ */
+ voting_power_timepoint: string
+ }
+ contracts: {
+ /**
+ * VeVote
+ */
+ vevote: string
+ /**
+ * Node Management
+ */
+ node_management: string
+ /**
+ * Stargate Nodes
+ */
+ stargate_nodes: string
+ }
+ vevote_contract: {
+ /**
+ * Contract Address:
+ */
+ contract_address: string
+ /**
+ * VeVote Contract Information
+ */
+ title: string
+ /**
+ * Loading VeVote Contract Information...
+ */
+ loading: string
+ /**
+ * Error loading VeVote contract data: {error}
+ * @param {string} error
+ */
+ error: RequiredParams<'error'>
+ /**
+ * No VeVote contract data available
+ */
+ no_data: string
+ /**
+ * Contract Version
+ */
+ contract_version: string
+ /**
+ * Quorum Numerator
+ */
+ quorum_numerator: string
+ /**
+ * Quorum Denominator
+ */
+ quorum_denominator: string
+ /**
+ * Min Voting Delay
+ */
+ min_voting_delay: string
+ /**
+ * Min Voting Duration
+ */
+ min_voting_duration: string
+ /**
+ * Max Voting Duration
+ */
+ max_voting_duration: string
+ /**
+ * Min Staked Amount
+ */
+ min_staked_amount: string
+ /**
+ * {percentage}% required
+ * @param {number} percentage
+ */
+ quorum_percentage: RequiredParams<'percentage'>
+ /**
+ * Contract Information
+ */
+ contract_info_title: string
+ }
+ node_management: {
+ /**
+ * Node Management Contract Information
+ */
+ title: string
+ /**
+ * Enter a wallet address to view detailed node ownership and delegation information for that account.
+ */
+ help_text: string
+ /**
+ * User Address
+ */
+ user_address_label: string
+ /**
+ * Enter user address (0x...)
+ */
+ user_address_placeholder: string
+ /**
+ * Load User Node Info
+ */
+ load_button: string
+ /**
+ * Loading...
+ */
+ loading_button: string
+ /**
+ * Loading user node information...
+ */
+ loading_text: string
+ /**
+ * Node Information for {address}
+ * @param {string} address
+ */
+ node_info_title: RequiredParams<'address'>
+ /**
+ * Is Node Holder
+ */
+ is_node_holder: string
+ /**
+ * Is Node Delegator
+ */
+ is_node_delegator: string
+ /**
+ * Owned Nodes
+ */
+ owned_nodes: string
+ /**
+ * Managed Nodes
+ */
+ managed_nodes: string
+ /**
+ * IDs: {ids}
+ * @param {string} ids
+ */
+ ids_label: RequiredParams<'ids'>
+ /**
+ * Yes
+ */
+ yes: string
+ /**
+ * No
+ */
+ no: string
+ /**
+ * Error loading node data: {error}
+ * @param {string} error
+ */
+ error: RequiredParams<'error'>
+ /**
+ * Node Information
+ */
+ card_title: string
+ /**
+ * Results for {address}
+ * @param {string} address
+ */
+ results_for: RequiredParams<'address'>
+ /**
+ * No node information available for this address
+ */
+ no_results: string
+ /**
+ * Available Methods
+ */
+ methods_title: string
+ /**
+ * This component demonstrates the NodeManagementService functionality. You can extend it to show additional statistics like total nodes, delegation stats, etc.
+ */
+ methods_description: string
+ }
+ stargate_nodes: {
+ /**
+ * Stargate NFT Contract Information
+ */
+ title: string
+ /**
+ * Loading Stargate NFT Information...
+ */
+ loading: string
+ /**
+ * Error loading Stargate NFT data: {error}
+ * @param {string} error
+ */
+ error: RequiredParams<'error'>
+ /**
+ * No Stargate NFT data available
+ */
+ no_data: string
+ /**
+ * Total Supply
+ */
+ total_supply: string
+ /**
+ * Available Levels
+ */
+ available_levels: string
+ /**
+ * Level IDs: {ids}
+ * @param {string} ids
+ */
+ level_ids: RequiredParams<'ids'>
+ /**
+ * Level Details
+ */
+ level_details_title: string
+ table: {
+ /**
+ * Level
+ */
+ level: string
+ /**
+ * Name
+ */
+ name: string
+ /**
+ * Is X-Node
+ */
+ is_x_node: string
+ /**
+ * Maturity Blocks
+ */
+ maturity_blocks: string
+ /**
+ * VET Required
+ */
+ vet_required: string
+ /**
+ * Circulating
+ */
+ circulating: string
+ /**
+ * Cap
+ */
+ cap: string
+ }
+ /**
+ * Yes
+ */
+ yes: string
+ /**
+ * No
+ */
+ no: string
+ /**
+ * N/A
+ */
+ not_available: string
+ /**
+ * Contract Information
+ */
+ contract_info_title: string
+ /**
+ * This displays comprehensive information about the Stargate NFT contract including level configurations, supply information, and staking requirements.
+ */
+ contract_description: string
+ }
+ /**
+ * {number}s
+ * @param {number} number
+ */
+ format_seconds: RequiredParams<'number'>
+ /**
+ * {minutes} min ({seconds}s)
+ * @param {number} minutes
+ * @param {number} seconds
+ */
+ format_minutes_seconds: RequiredParams<'minutes' | 'seconds'>
+ /**
+ * {number} days
+ * @param {number} number
+ */
+ format_days: RequiredParams<'number'>
+ /**
+ * {amount} VET
+ * @param {string} amount
+ */
+ vet_format: RequiredParams<'amount'>
+ governance_settings: {
+ /**
+ * Governance Settings
+ */
+ title: string
+ /**
+ * Configure VeVote governance parameters
+ */
+ description: string
+ /**
+ * Quorum Numerator
+ */
+ quorum_numerator_label: string
+ /**
+ * Required votes numerator (current: {current})
+ * @param {number} current
+ */
+ quorum_numerator_help: RequiredParams<'current'>
+ /**
+ * Min Voting Delay
+ */
+ min_voting_delay_label: string
+ /**
+ * Minimum delay before voting starts (in blocks)
+ */
+ min_voting_delay_help: string
+ /**
+ * Min Voting Duration
+ */
+ min_voting_duration_label: string
+ /**
+ * Minimum voting duration (in blocks)
+ */
+ min_voting_duration_help: string
+ /**
+ * Max Voting Duration
+ */
+ max_voting_duration_label: string
+ /**
+ * Maximum voting duration (in blocks)
+ */
+ max_voting_duration_help: string
+ /**
+ * Min Staked VET Amount
+ */
+ min_staked_vet_amount_label: string
+ /**
+ * Minimum VET amount required to stake
+ */
+ min_staked_vet_amount_help: string
+ /**
+ * Level ID Multipliers
+ */
+ level_multipliers_label: string
+ /**
+ * Voting weight multipliers for each level ID (scaled by 100)
+ */
+ level_multipliers_help: string
+ /**
+ * Validator
+ */
+ validator_multiplier: string
+ /**
+ * Strength
+ */
+ strength: string
+ /**
+ * Thunder
+ */
+ thunder: string
+ /**
+ * Mjolnir
+ */
+ mjolnir: string
+ /**
+ * VeThor X
+ */
+ vethor_x: string
+ /**
+ * Strength X
+ */
+ strength_x: string
+ /**
+ * Thunder X
+ */
+ thunder_x: string
+ /**
+ * Mjolnir X
+ */
+ mjolnir_x: string
+ /**
+ * Dawn Node
+ */
+ dawn: string
+ /**
+ * Lightning Node
+ */
+ lightning: string
+ /**
+ * Flash Node
+ */
+ flash: string
+ /**
+ * Level ID
+ */
+ level_id: string
+ /**
+ * Node Name
+ */
+ node_name: string
+ /**
+ * Current
+ */
+ current_multiplier: string
+ /**
+ * New Value
+ */
+ new_multiplier: string
+ /**
+ * Update Settings
+ */
+ update_settings: string
+ /**
+ * Update Governance Settings
+ */
+ update_governance_settings: string
+ /**
+ * Update Multipliers
+ */
+ update_multipliers: string
+ /**
+ * Updating...
+ */
+ updating: string
+ /**
+ * Settings Updated Successfully
+ */
+ success_title: string
+ /**
+ * Governance settings have been updated and are now active.
+ */
+ success_description: string
+ /**
+ * Failed to Update Settings
+ */
+ error_title: string
+ /**
+ * There was an error updating the governance settings: {error}
+ * @param {string} error
+ */
+ error_description: RequiredParams<'error'>
+ /**
+ * Value must be between {min} and {max}
+ * @param {number} max
+ * @param {number} min
+ */
+ invalid_range: RequiredParams<'max' | 'min'>
+ /**
+ * This field is required
+ */
+ required_field: string
+ /**
+ * Current: {value}
+ * @param {string} value
+ */
+ current_value: RequiredParams<'value'>
+ }
+ user_management: {
+ /**
+ * User Management
+ */
+ title: string
+ /**
+ * Grant or revoke roles for VeVote governance
+ */
+ description: string
+ /**
+ * User Address
+ */
+ user_address_label: string
+ /**
+ * Enter user address (0x...)
+ */
+ user_address_placeholder: string
+ /**
+ * Role
+ */
+ role_label: string
+ /**
+ * Select a role
+ */
+ role_placeholder: string
+ /**
+ * Current Roles:
+ */
+ current_roles_label: string
+ /**
+ * Checking roles...
+ */
+ checking_roles: string
+ /**
+ * No roles assigned
+ */
+ no_roles_assigned: string
+ }
+ role_users: {
+ /**
+ * Role Users
+ */
+ title: string
+ /**
+ * Select a role to view all users who have been granted that specific role.
+ */
+ help_text: string
+ /**
+ * Role
+ */
+ role_label: string
+ /**
+ * Select a role to query
+ */
+ role_placeholder: string
+ /**
+ * Query Role Users
+ */
+ query_button: string
+ /**
+ * Loading...
+ */
+ loading: string
+ /**
+ * Fetching users with selected role...
+ */
+ loading_text: string
+ /**
+ * Users with Role
+ */
+ results_title: string
+ /**
+ * {count} {count|{1: user, *: users}}
+ * @param {number | '1' | string} count
+ */
+ user_count: RequiredParams<'count' | `count|{1:${string}, *:${string}}`>
+ /**
+ * Role: {role}
+ * @param {string} role
+ */
+ role_selected: RequiredParams<'role'>
+ /**
+ * Granted: {date}
+ * @param {string} date
+ */
+ granted_at: RequiredParams<'date'>
+ /**
+ * View TX
+ */
+ view_tx: string
+ /**
+ * No users found with this role
+ */
+ no_users: string
+ /**
+ * Scroll to see more users
+ */
+ scrollable_hint: string
+ /**
+ * Error fetching role users: {error}
+ * @param {string} error
+ */
+ error_description: RequiredParams<'error'>
+ }
+ user_role_checker: {
+ /**
+ * Your Permissions
+ */
+ title: string
+ /**
+ * Connect wallet to see your roles
+ */
+ connect_wallet_message: string
+ /**
+ * Checking your roles...
+ */
+ checking_roles: string
+ }
+ voting_power_timepoint: {
+ /**
+ * Address:
+ */
+ address: string
+ /**
+ * Timepoint:
+ */
+ timepoint: string
+ /**
+ * Master Address:
+ */
+ master_address: string
+ /**
+ * Voting Power at Timepoint
+ */
+ title: string
+ /**
+ * Query historical voting power for any wallet at a specific timepoint
+ */
+ description: string
+ /**
+ * Wallet Address
+ */
+ address_label: string
+ /**
+ * Enter wallet address (0x...)
+ */
+ address_placeholder: string
+ /**
+ * Timepoint
+ */
+ timepoint_label: string
+ /**
+ * Enter timepoint
+ */
+ timepoint_placeholder: string
+ /**
+ * Master Address
+ */
+ master_address_label: string
+ /**
+ * Enter master address for validator power (0x...)
+ */
+ master_address_placeholder: string
+ /**
+ * Query Voting Power
+ */
+ query_button: string
+ /**
+ * Querying...
+ */
+ querying: string
+ /**
+ * Voting Power Results
+ */
+ results_title: string
+ /**
+ * Node-based Power:
+ */
+ node_power_label: string
+ /**
+ * Validator Power:
+ */
+ validator_power_label: string
+ /**
+ * Total Power:
+ */
+ total_power_label: string
+ /**
+ * No results to display
+ */
+ no_results: string
+ /**
+ * Please enter a valid address
+ */
+ invalid_address: string
+ /**
+ * Please enter a valid timepoint/block number
+ */
+ invalid_timepoint: string
+ /**
+ * Wallet address is required
+ */
+ address_required: string
+ /**
+ * Timepoint is required
+ */
+ timepoint_required: string
+ /**
+ * Query Failed
+ */
+ error_title: string
+ /**
+ * There was an error querying voting power: {error}
+ * @param {string} error
+ */
+ error_description: RequiredParams<'error'>
+ /**
+ * Enter a wallet address and timepoint to view historical voting power. Optionally provide a master address to check validator-delegated power.
+ */
+ help_text: string
+ }
+ /**
+ * Unknown error
+ */
+ unknown_error: string
+ /**
+ * This operation is sensitive. Please use with caution.
+ */
+ sensitive_operation_warning: string
+ /**
+ * Your Permissions
+ */
+ your_permissions: string
+ common_roles: {
+ /**
+ * Default Admin
+ */
+ DEFAULT_ADMIN_ROLE: string
+ /**
+ * Executor
+ */
+ EXECUTOR_ROLE: string
+ /**
+ * Settings Manager
+ */
+ SETTINGS_MANAGER_ROLE: string
+ /**
+ * Node Weight Manager
+ */
+ NODE_WEIGHT_MANAGER_ROLE: string
+ /**
+ * Upgrader
+ */
+ UPGRADER_ROLE: string
+ /**
+ * Whitelisted
+ */
+ WHITELISTED_ROLE: string
+ /**
+ * Whitelist Admin
+ */
+ WHITELIST_ADMIN_ROLE: string
+ /**
+ * Pauser
+ */
+ PAUSER_ROLE: string
+ /**
+ * Level Operator
+ */
+ LEVEL_OPERATOR_ROLE: string
+ /**
+ * Manager
+ */
+ MANAGER_ROLE: string
+ /**
+ * Whitelister
+ */
+ WHITELISTER_ROLE: string
+ /**
+ * Grant Role
+ */
+ grant_role: string
+ /**
+ * Revoke Role
+ */
+ revoke_role: string
+ /**
+ * Granting...
+ */
+ granting: string
+ /**
+ * Revoking...
+ */
+ revoking: string
+ /**
+ * Role Granted Successfully
+ */
+ grant_success_title: string
+ /**
+ * The role has been granted to the user.
+ */
+ grant_success_description: string
+ /**
+ * Role Revoked Successfully
+ */
+ revoke_success_title: string
+ /**
+ * The role has been revoked from the user.
+ */
+ revoke_success_description: string
+ /**
+ * Role Operation Failed
+ */
+ error_title: string
+ /**
+ * There was an error with the role operation: {error}
+ * @param {string} error
+ */
+ error_description: RequiredParams<'error'>
+ /**
+ * Please enter a valid address
+ */
+ invalid_address: string
+ /**
+ * Please select a role
+ */
+ role_required: string
+ /**
+ * User address is required
+ */
+ address_required: string
+ }
+ }
+}
export type TranslationFunctions = {
- /**
- * Left
- */
- left: () => LocalizedString;
- /**
- * by
- */
- by: () => LocalizedString;
- /**
- * Not published
- */
- not_published: () => LocalizedString;
- /**
- * Homepage
- */
- homepage: () => LocalizedString;
- /**
- * Back
- */
- back: () => LocalizedString;
- /**
- * Start
- */
- start: () => LocalizedString;
- /**
- * End
- */
- end: () => LocalizedString;
- /**
- * Edit
- */
- edit: () => LocalizedString;
- /**
- * On
- */
- on: () => LocalizedString;
- /**
- * All
- */
- all: () => LocalizedString;
- /**
- * Finished
- */
- finished: () => LocalizedString;
- /**
- * Executed
- */
- executed: () => LocalizedString;
- /**
- * Show more
- */
- show_more: () => LocalizedString;
- /**
- * Exit
- */
- exit: () => LocalizedString;
- /**
- * Next
- */
- next: () => LocalizedString;
- /**
- * Learn more
- */
- learn_more: () => LocalizedString;
- /**
- * See details
- */
- see_details: () => LocalizedString;
- /**
- * Select
- */
- select: () => LocalizedString;
- /**
- * Select between
- */
- select_between: () => LocalizedString;
- /**
- * and
- */
- and: () => LocalizedString;
- /**
- * one
- */
- one: () => LocalizedString;
- /**
- * Voting
- */
- voting: () => LocalizedString;
- /**
- * Voters
- */
- voters: () => LocalizedString;
- /**
- * Most voted
- */
- most_voted: () => LocalizedString;
- /**
- * Votes
- */
- votes: () => LocalizedString;
- /**
- * Delete
- */
- delete: () => LocalizedString;
- /**
- * Cancel
- */
- cancel: () => LocalizedString;
- /**
- * %
- */
- percentage: () => LocalizedString;
- /**
- * Submit
- */
- submit: () => LocalizedString;
- /**
- * Submit Vote
- */
- submit_vote: () => LocalizedString;
- /**
- * Voting power
- */
- voting_power: () => LocalizedString;
- /**
- * Your voting power
- */
- your_voting_power: () => LocalizedString;
- /**
- * Voted
- */
- voted: () => LocalizedString;
- /**
- * Vote
- */
- vote: () => LocalizedString;
- /**
- * Wallet
- */
- wallet: () => LocalizedString;
- /**
- * Block #
- */
- block: () => LocalizedString;
- /**
- * Node
- */
- node: () => LocalizedString;
- /**
- * Go back
- */
- go_back: () => LocalizedString;
- /**
- * Optional
- */
- optional: () => LocalizedString;
- /**
- * Buy a Node
- */
- buy_a_node: () => LocalizedString;
- /**
- * {current}/{max}
- */
- filed_length: (arg: { current: number; max: number }) => LocalizedString;
- /**
- * Upload
- */
- upload: () => LocalizedString;
- /**
- * Copied to clipboard
- */
- copied_to_clipboard: () => LocalizedString;
- /**
+ /**
+ * Left
+ */
+ left: () => LocalizedString
+ /**
+ * by
+ */
+ by: () => LocalizedString
+ /**
+ * Not published
+ */
+ not_published: () => LocalizedString
+ /**
+ * Homepage
+ */
+ homepage: () => LocalizedString
+ /**
+ * Back
+ */
+ back: () => LocalizedString
+ /**
+ * Start
+ */
+ start: () => LocalizedString
+ /**
+ * End
+ */
+ end: () => LocalizedString
+ /**
+ * Edit
+ */
+ edit: () => LocalizedString
+ /**
+ * On
+ */
+ on: () => LocalizedString
+ /**
+ * All
+ */
+ all: () => LocalizedString
+ /**
+ * Finished
+ */
+ finished: () => LocalizedString
+ /**
+ * Executed
+ */
+ executed: () => LocalizedString
+ /**
+ * Show more
+ */
+ show_more: () => LocalizedString
+ /**
+ * Exit
+ */
+ exit: () => LocalizedString
+ /**
+ * Next
+ */
+ next: () => LocalizedString
+ /**
+ * Learn more
+ */
+ learn_more: () => LocalizedString
+ /**
+ * See details
+ */
+ see_details: () => LocalizedString
+ /**
+ * Select
+ */
+ select: () => LocalizedString
+ /**
+ * Select between
+ */
+ select_between: () => LocalizedString
+ /**
+ * and
+ */
+ and: () => LocalizedString
+ /**
+ * one
+ */
+ one: () => LocalizedString
+ /**
+ * Voting
+ */
+ voting: () => LocalizedString
+ /**
+ * Voters
+ */
+ voters: () => LocalizedString
+ /**
+ * Most voted
+ */
+ most_voted: () => LocalizedString
+ /**
+ * Votes
+ */
+ votes: () => LocalizedString
+ /**
+ * Delete
+ */
+ 'delete': () => LocalizedString
+ /**
+ * Cancel
+ */
+ cancel: () => LocalizedString
+ /**
+ * %
+ */
+ percentage: () => LocalizedString
+ /**
+ * Submit
+ */
+ submit: () => LocalizedString
+ /**
+ * Submit Vote
+ */
+ submit_vote: () => LocalizedString
+ /**
+ * Voting power
+ */
+ voting_power: () => LocalizedString
+ /**
+ * Your voting power
+ */
+ your_voting_power: () => LocalizedString
+ /**
+ * Voted
+ */
+ voted: () => LocalizedString
+ /**
+ * Vote
+ */
+ vote: () => LocalizedString
+ /**
+ * Wallet
+ */
+ wallet: () => LocalizedString
+ /**
+ * Block #
+ */
+ block: () => LocalizedString
+ /**
+ * Node
+ */
+ node: () => LocalizedString
+ /**
+ * Go back
+ */
+ go_back: () => LocalizedString
+ /**
+ * Optional
+ */
+ optional: () => LocalizedString
+ /**
+ * Buy a Node
+ */
+ buy_a_node: () => LocalizedString
+ /**
+ * {current}/{max}
+ */
+ filed_length: (arg: { current: number, max: number }) => LocalizedString
+ /**
+ * Upload
+ */
+ upload: () => LocalizedString
+ /**
+ * Copied to clipboard
+ */
+ copied_to_clipboard: () => LocalizedString
+ /**
* Image size should be 1280x512px (ratio 3:1).
JPG, PNG or SVG of maximum of {size}MB.
*/
- file_upload_description: (arg: { size: number }) => LocalizedString;
- /**
- * Select date
- */
- select_date: () => LocalizedString;
- /**
- * Select time
- */
- select_time: () => LocalizedString;
- /**
- * Maximum
- */
- maximum: () => LocalizedString;
- /**
- * Minimum
- */
- minimum: () => LocalizedString;
- /**
- * Option {index}
- */
- number_option: (arg: { index: number }) => LocalizedString;
- /**
- * Continue
- */
- continue: () => LocalizedString;
- /**
- * Results
- */
- results: () => LocalizedString;
- /**
- * Description
- */
- description: () => LocalizedString;
- /**
- * Preview
- */
- preview: () => LocalizedString;
- /**
- * Close
- */
- close: () => LocalizedString;
- /**
- * Confirm
- */
- confirm: () => LocalizedString;
- /**
- * Try Again
- */
- try_again: () => LocalizedString;
- /**
- * Read full description
- */
- read_full_description: () => LocalizedString;
- /**
- * Disconnect
- */
- disconnect: () => LocalizedString;
- /**
- * Connect Wallet
- */
- connect_wallet: () => LocalizedString;
- /**
- * Connect your wallet to vote
- */
- connect_wallet_to_vote: () => LocalizedString;
- /**
- * Comment
- */
- comment: () => LocalizedString;
- /**
- * Add a comment to your vote...
- */
- comment_placeholder: () => LocalizedString;
- /**
- * Migrate
- */
- migrate: () => LocalizedString;
- /**
- * StarGate
- */
- stargate: () => LocalizedString;
- /**
- * How voting power is obtained
- */
- learn_how_voting_power: () => LocalizedString;
- /**
- * Join the discussion on Discourse
- */
- discuss_on_discourse: () => LocalizedString;
- datepicker: {
- /**
- * Select date
- */
- select_date: () => LocalizedString;
- /**
- * Previous month
- */
- previous_month: () => LocalizedString;
- /**
- * Next month
- */
- next_month: () => LocalizedString;
- /**
- * Today
- */
- today: () => LocalizedString;
- weekdays: {
- /**
- * Mon
- */
- mon: () => LocalizedString;
- /**
- * Tue
- */
- tue: () => LocalizedString;
- /**
- * Wed
- */
- wed: () => LocalizedString;
- /**
- * Thu
- */
- thu: () => LocalizedString;
- /**
- * Fri
- */
- fri: () => LocalizedString;
- /**
- * Sat
- */
- sat: () => LocalizedString;
- /**
- * Sun
- */
- sun: () => LocalizedString;
- };
- months: {
- /**
- * January
- */
- january: () => LocalizedString;
- /**
- * February
- */
- february: () => LocalizedString;
- /**
- * March
- */
- march: () => LocalizedString;
- /**
- * April
- */
- april: () => LocalizedString;
- /**
- * May
- */
- may: () => LocalizedString;
- /**
- * June
- */
- june: () => LocalizedString;
- /**
- * July
- */
- july: () => LocalizedString;
- /**
- * August
- */
- august: () => LocalizedString;
- /**
- * September
- */
- september: () => LocalizedString;
- /**
- * October
- */
- october: () => LocalizedString;
- /**
- * November
- */
- november: () => LocalizedString;
- /**
- * December
- */
- december: () => LocalizedString;
- };
- };
- timepicker: {
- /**
- * Select time
- */
- select_time: () => LocalizedString;
- /**
- * Select time (UTC)
- */
- select_time_24h: () => LocalizedString;
- /**
- * Hours
- */
- hours: () => LocalizedString;
- /**
- * Minutes
- */
- minutes: () => LocalizedString;
- /**
- * All times are in UTC
- */
- utc_notice: () => LocalizedString;
- };
- home: {
- /**
- * Home
- */
- title: () => LocalizedString;
- /**
- * Go to proposals
- */
- go_to_proposals: () => LocalizedString;
- };
- node_names: {
- /**
- * not defined
- */
- none: () => LocalizedString;
- /**
- * Strength
- */
- strength: () => LocalizedString;
- /**
- * Thunder
- */
- thunder: () => LocalizedString;
- /**
- * Mjolnir
- */
- mjolnir: () => LocalizedString;
- /**
- * VeThor X
- */
- vethorx: () => LocalizedString;
- /**
- * Strength X
- */
- strengthx: () => LocalizedString;
- /**
- * Thunder X
- */
- thunderx: () => LocalizedString;
- /**
- * Mjolnir X
- */
- mjolnirx: () => LocalizedString;
- /**
- * Flash
- */
- flash: () => LocalizedString;
- /**
- * Lightning
- */
- lightning: () => LocalizedString;
- /**
- * Dawn
- */
- dawn: () => LocalizedString;
- /**
- * Validator
- */
- validator: () => LocalizedString;
- /**
- * Validator (inactive)
- */
- inactive_validator: () => LocalizedString;
- };
- field_errors: {
- /**
- * Required
- */
- required: () => LocalizedString;
- /**
- * Invalid format
- */
- invalid_format: () => LocalizedString;
- /**
- * The end date must be after the start date
- */
- end_before_start: () => LocalizedString;
- /**
- * The end date must be in the future
- */
- end_before_today: () => LocalizedString;
- /**
- * The start date must be in the future
- */
- start_after_today: () => LocalizedString;
- /**
- * The end date must be within {days} days of the start date
- */
- end_after_max_duration: (arg: { days: string }) => LocalizedString;
- /**
- * No voters found matching your search criteria.
- */
- failed_load_voters: () => LocalizedString;
- descriptions_errors: {
- /**
- * Please replace placeholder text with your own content before submitting the proposal.
- */
- placeholders_not_replaced: () => LocalizedString;
- /**
- * Description cannot be empty. Please provide content for your proposal.
- */
- empty_description: () => LocalizedString;
- };
- /**
- * The Discourse topic does not exist or is not accessible
- */
- discourse_topic_not_exist: () => LocalizedString;
- };
- voting_list: {
- /**
- * Voting options:
- */
- voting_options: () => LocalizedString;
- /**
- * Select an option to vote:
- */
- option_to_vote: () => LocalizedString;
- /**
- * Voting has not started yet
- */
- voting_has_not_started_yet: () => LocalizedString;
- /**
- * Please connect your wallet
- */
- please_connect_your_wallet: () => LocalizedString;
- /**
- * You have already voted
- */
- you_have_already_voted: () => LocalizedString;
- /**
- * You don't have enough voting power
- */
- you_dont_have_enough_voting_power: () => LocalizedString;
- };
- proposal: {
- /**
- * Proposal
- */
- title: () => LocalizedString;
- /**
- * Proposed by
- */
- proposed_by: () => LocalizedString;
- /**
- * Voting calendar
- */
- voting_calendar: () => LocalizedString;
- /**
- * Confirm in your wallet...
- */
- confirm_in_your_wallet: () => LocalizedString;
- /**
- * Who can vote
- */
- who_can_vote: () => LocalizedString;
- /**
- * VeChain Foundation
- */
- vechain_foundation: () => LocalizedString;
- /**
- * Node holders with voting power will be able to vote on this proposal.
- */
- node_holders: () => LocalizedString;
- /**
- * Voting will start {date}
- */
- voting_will_start: (arg: { date: string }) => LocalizedString;
- /**
- * See your vote details
- */
- see_your_vote: () => LocalizedString;
- /**
- * See all ({voters}) voters
- */
- see_all_voters: (arg: { voters: number }) => LocalizedString;
- /**
- * See first voter
- */
- see_first_voter: () => LocalizedString;
- /**
- * Mark as executed
- */
- mark_as_executed: () => LocalizedString;
- /**
- * Buy another node to increase your voting power on future proposals.
- */
- buy_another_node: () => LocalizedString;
- /**
- * Voting is only possible for Node holders. Buy a node to vote on future proposals or increase your voting power.
- */
- buy_a_node: () => LocalizedString;
- vote_success: {
- /**
- * Vote submitted!
- */
- title: () => LocalizedString;
- /**
- * Your vote was submitted successfully.
- */
- description: () => LocalizedString;
- };
- cancel_proposal: {
- /**
- * Cancel proposal
- */
- title: () => LocalizedString;
- /**
- * Canceling the proposal means it the voting will not take place and the proposal will not have no results.
- */
- description: () => LocalizedString;
- /**
- * Reason
- */
- reason: () => LocalizedString;
- /**
- * Write the reason for cancellation...
- */
- reason_placeholder: () => LocalizedString;
- /**
- * Proposal canceled successfully
- */
- success_title: () => LocalizedString;
- /**
- * The proposal has been canceled successfully. Voting will not take place.
- */
- success_description: () => LocalizedString;
- };
- execute_proposal: {
- /**
- * Mark as Executed
- */
- title: () => LocalizedString;
- /**
- * If the actions of the proposal have already been executed, you can mark this approved proposal as executed for the voters to know.
- */
- description: () => LocalizedString;
- /**
- * Execution / Transaction details
- */
- label: () => LocalizedString;
- /**
- * Insert link with the proof of the execution
- */
- link_placeholder: () => LocalizedString;
- };
- delete_proposal: {
- /**
- * Delete proposal
- */
- title: () => LocalizedString;
- /**
- * If you delete this draft, all the information of the proposal will not be possible to recover anymore.
- */
- description: () => LocalizedString;
- /**
- * Are you sure you want to delete it?
- */
- confirmation: () => LocalizedString;
- /**
- * No, go back
- */
- no_go_back: () => LocalizedString;
- /**
- * Yes, Delete
- */
- yes_delete: () => LocalizedString;
- };
- info_box: {
- info: {
- /**
- * Minimum participation
- */
- title: () => LocalizedString;
- /**
- * A minimum of {quorum}% participation must be reached to validate the voting of the proposal and get approval.
- */
- description: (arg: { quorum: number }) => LocalizedString;
- };
- approved: {
- /**
- * Minimum participation reached
- */
- title: () => LocalizedString;
- /**
- * The voting participation reached the minimum required of {quorum}% to get approval.
- */
- description: (arg: { quorum: number }) => LocalizedString;
- };
- executed: {
- /**
- * Proposal Approved and Executed
- */
- title: () => LocalizedString;
- /**
- * The voting approved the proposal and the actions have been executed.
- */
- description: () => LocalizedString;
- };
- "min-not-reached": {
- /**
- * Minimum participation not reached
- */
- title: () => LocalizedString;
- /**
- * The voting participation didn’t reached the minimum required of {quorum}% to get approval.
- */
- description: (arg: { quorum: number }) => LocalizedString;
- };
- rejected: {
- /**
- * Proposal Rejected
- */
- title: () => LocalizedString;
- /**
- * The proposal didn’t get enough votes in favor to get approval.
- */
- description: () => LocalizedString;
- };
- canceled: {
- /**
- * Proposal Canceled
- */
- title: () => LocalizedString;
- /**
- * The proposal was canceled by VeChain or the proposer by the following reason:
- */
- description: () => LocalizedString;
- };
- };
- voting_power: {
- /**
- * Get more voting power
- */
- get_more_voting_power: () => LocalizedString;
- /**
- * Get voting power
- */
- get_voting_power: () => LocalizedString;
- /**
- * Voting power
- */
- title: () => LocalizedString;
- /**
- * Your voting power was calculated at the time of the snapshot {snapshot}.
- */
- calculation: (arg: { snapshot: string }) => LocalizedString;
- /**
- * Total voting power
- */
- total_voting_power: () => LocalizedString;
- warnings: {
- /**
- * The connected wallet has no voting power
- */
- zero_voting_power: () => LocalizedString;
- /**
- * You have legacy nodes that haven’t been migrated yet. Migrate to get more voting power.
- */
- legacy_node: () => LocalizedString;
- delegated: {
- /**
- * Your voting power is delegated
- */
- title: () => LocalizedString;
- /**
- * Your voting power is delegated to another node
- */
- description: () => LocalizedString;
- };
- };
- };
- voters_table: {
- filters: {
- /**
- * Search by address...
- */
- search_by_address: () => LocalizedString;
- /**
- * Voting options
- */
- voting_options: () => LocalizedString;
- /**
- * Node
- */
- node: () => LocalizedString;
- /**
- * Sort by
- */
- sort_by: () => LocalizedString;
- };
- header: {
- /**
- * Date
- */
- date: () => LocalizedString;
- /**
- * Address
- */
- address: () => LocalizedString;
- /**
- * Node
- */
- node: () => LocalizedString;
- /**
- * Node ID
- */
- node_id: () => LocalizedString;
- /**
- * Power
- */
- voting_power: () => LocalizedString;
- /**
- * Option
- */
- voted_option: () => LocalizedString;
- /**
- * Transaction ID
- */
- transaction_id: () => LocalizedString;
- };
- };
- create: {
- /**
- * Previewing proposal
- */
- previewing: () => LocalizedString;
- /**
- * Create Proposal
- */
- title: () => LocalizedString;
- /**
- * {current} of {total}
- */
- steps: (arg: { current: number; total: number }) => LocalizedString;
- /**
- * Add the main details and setup the calendar
- */
- voting_details_desc: () => LocalizedString;
- /**
- * Define the voting setup details
- */
- voting_setup_desc: () => LocalizedString;
- /**
- * Review all the details before publishing
- */
- voting_summary_desc: () => LocalizedString;
- /**
- * Add new option
- */
- add_new_option: () => LocalizedString;
- exit_proposal: {
- /**
- * Exit proposal creation
- */
- title: () => LocalizedString;
- /**
- * By exiting you lose all the information written or you can save this proposal as a draft and finish later?
- */
- description: () => LocalizedString;
- /**
- * You will lose all entered information if you exit now. Saving as a draft is only available on the final step of the form.
- */
- description_last_step: () => LocalizedString;
- /**
- * Be aware that a draft proposal already exists. Saving now will overwrite your previous draft.
- */
- description_draft_exist: () => LocalizedString;
- /**
- * Exit proposal
- */
- exit_button: () => LocalizedString;
- /**
- * Save draft
- */
- save_button: () => LocalizedString;
- };
- draft_saved: {
- /**
- * Draft saved!
- */
- title: () => LocalizedString;
- /**
- * The proposal draft has been saved successfully and can now be continued later.
- */
- description: () => LocalizedString;
- };
- details_form: {
- /**
- * Title
- */
- title: () => LocalizedString;
- /**
- * What's the proposal title?
- */
- title_placeholder: () => LocalizedString;
- /**
- * Description
- */
- description: () => LocalizedString;
- /**
- * Add a description...
- */
- description_placeholder: () => LocalizedString;
- /**
- * Header image
- */
- header_image: () => LocalizedString;
- /**
- * Discourse url
- */
- discourse_url: () => LocalizedString;
- /**
- * Discourse Topic
- */
- discourse_topic: () => LocalizedString;
- /**
- * your-topic-name
- */
- discourse_topic_placeholder: () => LocalizedString;
- /**
- * Enter the topic name from your VeChain Discourse discussion
- */
- discourse_topic_help: () => LocalizedString;
- /**
- * Voting calendar
- */
- voting_calendar: () => LocalizedString;
- };
- setup_form: {
- /**
- * Voting type
- */
- voting_type: () => LocalizedString;
- /**
- * Select the type
- */
- voting_type_subtitle: () => LocalizedString;
- /**
- * Voting Question
- */
- voting_question: () => LocalizedString;
- /**
- * This question should provide exact context to the voting options:
- */
- voting_question_subtitle: () => LocalizedString;
- /**
- * Write the question...
- */
- voting_question_placeholder: () => LocalizedString;
- /**
- * Voting limit
- */
- voting_limit: () => LocalizedString;
- /**
- * Define the minimum and maximum amount of options allowed per voter:
- */
- voting_limit_subtitle: () => LocalizedString;
- /**
- * Voting options
- */
- voting_options: () => LocalizedString;
- /**
- * The “single choice” voting type only allows the voter to select:
- */
- voting_choice_subtitle: () => LocalizedString;
- /**
- * Add between 2 and 30 options to vote:
- */
- voting_options_subtitle: () => LocalizedString;
- /**
- * Add new option
- */
- add_new_option: () => LocalizedString;
- /**
- * Write the voting option...
- */
- voting_option_placeholder: () => LocalizedString;
- };
- summary_form: {
- main_details: {
- /**
- * Main details
- */
- title: () => LocalizedString;
- /**
- * Calendar
- */
- calendar: () => LocalizedString;
- };
- voting_setup: {
- /**
- * Voting setup
- */
- title: () => LocalizedString;
- /**
- * Question
- */
- question: () => LocalizedString;
- /**
- * Type
- */
- type: () => LocalizedString;
- /**
- * Minimum {min} options - Maximum {limit} options
- */
- limit: (arg: { limit: number; min: number }) => LocalizedString;
- types: {
- /**
- * Single choice - For / Against / Abstain
- */
- SINGLE_CHOICE: () => LocalizedString;
- };
- };
- /**
- * Publish Proposal
- */
- publish_proposal: () => LocalizedString;
- /**
- * Please note that once the campaign is published, it can't be edited anymore.
- */
- publish_description: () => LocalizedString;
- /**
- * Are you sure you want to publish this proposal?
- */
- publish_sub_description: () => LocalizedString;
- /**
- * Publishing failed
- */
- publish_failed: () => LocalizedString;
- /**
- * The publishing of the proposal couldn’t be completed. Please try again.
- */
- publish_failed_description: () => LocalizedString;
- /**
- * Proposal published
- */
- publish_success: () => LocalizedString;
- /**
- * The proposal has been successfully publish and can now be seen publicly.
- */
- publish_success_description: () => LocalizedString;
- };
- };
- /**
- * Go to StarGate
- */
- go_to_stargate: () => LocalizedString;
- /**
- * Proposal not found
- */
- proposal_not_found: () => LocalizedString;
- /**
- * The proposal you're looking for doesn't exist or may have been removed. It's possible the URL is incorrect or the proposal has been deleted.
- */
- proposal_not_found_description: () => LocalizedString;
- /**
- * Back to Proposals
- */
- back_to_proposals: () => LocalizedString;
- /**
- * Try Again
- */
- try_again: () => LocalizedString;
- /**
- * Starts in
- */
- starts_in: () => LocalizedString;
- /**
- * Ends in
- */
- ends_in: () => LocalizedString;
- /**
- * Timeline
- */
- timeline: () => LocalizedString;
- /**
- * Created
- */
- timeline_created: () => LocalizedString;
- /**
- * Proposal Canceled
- */
- proposal_canceled: () => LocalizedString;
- /**
- * The proposal was canceled by VeChain or the proposer by the following reason:
- */
- proposal_canceled_description: () => LocalizedString;
- /**
- * No reason provided
- */
- no_reason_provided: () => LocalizedString;
- /**
- * Unknown error
- */
- unknown_error: () => LocalizedString;
- /**
- * Failed to execute proposal
- */
- failed_to_execute_proposal: () => LocalizedString;
- /**
- * Proposal Approved and Executed
- */
- proposal_approved_and_executed: () => LocalizedString;
- /**
- * The voting participation reached the minimum quorum to get approval.
- */
- proposal_approved: () => LocalizedString;
- /**
- * The voting approved the proposal and the actions have been executed.
- */
- the_voting_approved_the_proposal_and_the_actions_have_been_executed: () => LocalizedString;
- /**
- * See details
- */
- see_details: () => LocalizedString;
- /**
- * Proposal Rejected
- */
- proposal_rejected: () => LocalizedString;
- /**
- * The proposal didn't get enough votes in favor to get approval.
- */
- the_proposal_didnt_get_enough_votes_in_favor_to_get_approval: () => LocalizedString;
- /**
- * Minimum participant not met
- */
- minimum_quorum_not_reached: () => LocalizedString;
- /**
- * Quorum of {quorum} voting power not reached.
- */
- quorum_not_reached: (arg: { quorum: string }) => LocalizedString;
- /**
- * Quorum of {quorum} voting power not reached yet.
- */
- quorum_not_reached_yet: (arg: { quorum: string }) => LocalizedString;
- /**
- * Quorum reached.
- */
- quorum_reached: () => LocalizedString;
- /**
- * Vote submission failed
- */
- vote_submission_failed: () => LocalizedString;
- /**
- * Vote submitted successfully!
- */
- vote_submitted_successfully: () => LocalizedString;
- /**
- * Submit your vote
- */
- submit_your_vote: () => LocalizedString;
- /**
- * Your vote cannot be changed once submitted.
- */
- vote_cannot_be_changed: () => LocalizedString;
- /**
- * Waiting wallet confirmation...
- */
- waiting_wallet_confirmation: () => LocalizedString;
- /**
- * Confirm vote
- */
- confirm_vote: () => LocalizedString;
- /**
- * You voted
- */
- you_voted: () => LocalizedString;
- };
- proposals: {
- /**
- * Proposals
- */
- title: () => LocalizedString;
- /**
- * Create Proposal
- */
- create: () => LocalizedString;
- /**
- * Search proposals...
- */
- search_placeholder: () => LocalizedString;
- /**
- * No proposals found
- */
- no_proposals: () => LocalizedString;
- /**
- * {current} of {total} proposals
- */
- pagination: (arg: { current: number; total: number }) => LocalizedString;
- };
- statuses: {
- /**
- * Voted
- */
- voted: () => LocalizedString;
- };
- badge: {
- /**
- * Draft
- */
- draft: () => LocalizedString;
- /**
- * Upcoming
- */
- upcoming: () => LocalizedString;
- /**
- * Voting now
- */
- voting: () => LocalizedString;
- /**
- * Approved
- */
- approved: () => LocalizedString;
- /**
- * Executed
- */
- executed: () => LocalizedString;
- /**
- * Canceled
- */
- canceled: () => LocalizedString;
- /**
- * Rejected
- */
- rejected: () => LocalizedString;
- /**
- * Quorum not reached
- */
- "min-not-reached": () => LocalizedString;
- };
- filters: {
- /**
- * Filters
- */
- title: () => LocalizedString;
- /**
- * Apply
- */
- apply: () => LocalizedString;
- /**
- * Reset
- */
- reset: () => LocalizedString;
- /**
- * Select all
- */
- select_all: () => LocalizedString;
- /**
- * Deselect all
- */
- deselect_all: () => LocalizedString;
- sort: {
- /**
- * Newest
- */
- newest: () => LocalizedString;
- /**
- * Oldest
- */
- oldest: () => LocalizedString;
- /**
- * Most participant
- */
- most_participant: () => LocalizedString;
- /**
- * Least participant
- */
- least_participant: () => LocalizedString;
- };
- };
- header: {
- /**
- * VeChainThor Voting Platform
- */
- title: () => LocalizedString;
- /**
- * Vote to shape the future of VeChainThor
- */
- description: () => LocalizedString;
- /**
- * How to vote
- */
- how_to_vote: () => LocalizedString;
- /**
- * Get voting power
- */
- how_to_get_voting_power: () => LocalizedString;
- };
- stargate_warning: {
- /**
- * StarGate Node Migration Required
- */
- title: () => LocalizedString;
- /**
- * You have 1 or more non-migrated nodes. Please migrate them as soon as possible to continue voting.
- */
- description: () => LocalizedString;
- /**
- * https://app.stargate.vechain.org/
- */
- migration_link: () => LocalizedString;
- /**
- * If a proposal has already started, you will not be able to vote on it even after migration. Only future proposals will be available for voting.
- */
- ongoing_proposal_warning: () => LocalizedString;
- /**
- * If you want to continue anyway, write this text: agree-with-this
- */
- confirmation_instruction: () => LocalizedString;
- /**
- * Please type exactly 'agree-with-this' to continue
- */
- confirmation_error: () => LocalizedString;
- };
- footer: {
- /**
- * v1.0.0
- */
- version: () => LocalizedString;
- /**
- * All Rights Reserved © Vechain Foundation San Marino S.r.l.
- */
- all_right: () => LocalizedString;
- legal: {
- /**
- * Legal
- */
- title: () => LocalizedString;
- /**
- * Terms of Service
- */
- terms_of_service: () => LocalizedString;
- /**
- * Privacy Policy
- */
- privacy_policy: () => LocalizedString;
- /**
- * Cookies Policy
- */
- cookies_policy: () => LocalizedString;
- };
- resources: {
- /**
- * Resources
- */
- title: () => LocalizedString;
- /**
- * Docs
- */
- docs: () => LocalizedString;
- /**
- * StarGate
- */
- stargate: () => LocalizedString;
- /**
- * Support
- */
- support: () => LocalizedString;
- /**
- * Governance Charter
- */
- governance_charter: () => LocalizedString;
- };
- };
-};
+ file_upload_description: (arg: { size: number }) => LocalizedString
+ /**
+ * Select date
+ */
+ select_date: () => LocalizedString
+ /**
+ * Select time
+ */
+ select_time: () => LocalizedString
+ /**
+ * Maximum
+ */
+ maximum: () => LocalizedString
+ /**
+ * Minimum
+ */
+ minimum: () => LocalizedString
+ /**
+ * Option {index}
+ */
+ number_option: (arg: { index: number }) => LocalizedString
+ /**
+ * Continue
+ */
+ 'continue': () => LocalizedString
+ /**
+ * Results
+ */
+ results: () => LocalizedString
+ /**
+ * Description
+ */
+ description: () => LocalizedString
+ /**
+ * Preview
+ */
+ preview: () => LocalizedString
+ /**
+ * Close
+ */
+ close: () => LocalizedString
+ /**
+ * Confirm
+ */
+ confirm: () => LocalizedString
+ /**
+ * Try Again
+ */
+ try_again: () => LocalizedString
+ /**
+ * Read full description
+ */
+ read_full_description: () => LocalizedString
+ /**
+ * Disconnect
+ */
+ disconnect: () => LocalizedString
+ /**
+ * Connect Wallet
+ */
+ connect_wallet: () => LocalizedString
+ /**
+ * Connect your wallet to vote
+ */
+ connect_wallet_to_vote: () => LocalizedString
+ /**
+ * Comment
+ */
+ comment: () => LocalizedString
+ /**
+ * Add a comment to your vote...
+ */
+ comment_placeholder: () => LocalizedString
+ /**
+ * Migrate
+ */
+ migrate: () => LocalizedString
+ /**
+ * StarGate
+ */
+ stargate: () => LocalizedString
+ /**
+ * How voting power is obtained
+ */
+ learn_how_voting_power: () => LocalizedString
+ /**
+ * Join the discussion on Discourse
+ */
+ discuss_on_discourse: () => LocalizedString
+ common: {
+ time: {
+ /**
+ * {count} {count|{1: second, *: seconds}}
+ */
+ seconds: (arg: { count: number | '1' | string }) => LocalizedString
+ /**
+ * {count} {count|{1: minute, *: minutes}}
+ */
+ minutes: (arg: { count: number | '1' | string }) => LocalizedString
+ /**
+ * {count} {count|{1: hour, *: hours}}
+ */
+ hours: (arg: { count: number | '1' | string }) => LocalizedString
+ /**
+ * {count} {count|{1: day, *: days}}
+ */
+ days: (arg: { count: number | '1' | string }) => LocalizedString
+ }
+ }
+ datepicker: {
+ /**
+ * Select date
+ */
+ select_date: () => LocalizedString
+ /**
+ * Previous month
+ */
+ previous_month: () => LocalizedString
+ /**
+ * Next month
+ */
+ next_month: () => LocalizedString
+ /**
+ * Today
+ */
+ today: () => LocalizedString
+ weekdays: {
+ /**
+ * Mon
+ */
+ mon: () => LocalizedString
+ /**
+ * Tue
+ */
+ tue: () => LocalizedString
+ /**
+ * Wed
+ */
+ wed: () => LocalizedString
+ /**
+ * Thu
+ */
+ thu: () => LocalizedString
+ /**
+ * Fri
+ */
+ fri: () => LocalizedString
+ /**
+ * Sat
+ */
+ sat: () => LocalizedString
+ /**
+ * Sun
+ */
+ sun: () => LocalizedString
+ }
+ months: {
+ /**
+ * January
+ */
+ january: () => LocalizedString
+ /**
+ * February
+ */
+ february: () => LocalizedString
+ /**
+ * March
+ */
+ march: () => LocalizedString
+ /**
+ * April
+ */
+ april: () => LocalizedString
+ /**
+ * May
+ */
+ may: () => LocalizedString
+ /**
+ * June
+ */
+ june: () => LocalizedString
+ /**
+ * July
+ */
+ july: () => LocalizedString
+ /**
+ * August
+ */
+ august: () => LocalizedString
+ /**
+ * September
+ */
+ september: () => LocalizedString
+ /**
+ * October
+ */
+ october: () => LocalizedString
+ /**
+ * November
+ */
+ november: () => LocalizedString
+ /**
+ * December
+ */
+ december: () => LocalizedString
+ }
+ }
+ timepicker: {
+ /**
+ * Select time
+ */
+ select_time: () => LocalizedString
+ /**
+ * Select time (UTC)
+ */
+ select_time_24h: () => LocalizedString
+ /**
+ * Hours
+ */
+ hours: () => LocalizedString
+ /**
+ * Minutes
+ */
+ minutes: () => LocalizedString
+ /**
+ * All times are in UTC
+ */
+ utc_notice: () => LocalizedString
+ }
+ home: {
+ /**
+ * Home
+ */
+ title: () => LocalizedString
+ /**
+ * Go to proposals
+ */
+ go_to_proposals: () => LocalizedString
+ }
+ node_names: {
+ /**
+ * not defined
+ */
+ none: () => LocalizedString
+ /**
+ * Strength
+ */
+ strength: () => LocalizedString
+ /**
+ * Thunder
+ */
+ thunder: () => LocalizedString
+ /**
+ * Mjolnir
+ */
+ mjolnir: () => LocalizedString
+ /**
+ * VeThor X
+ */
+ vethorx: () => LocalizedString
+ /**
+ * Strength X
+ */
+ strengthx: () => LocalizedString
+ /**
+ * Thunder X
+ */
+ thunderx: () => LocalizedString
+ /**
+ * Mjolnir X
+ */
+ mjolnirx: () => LocalizedString
+ /**
+ * Flash
+ */
+ flash: () => LocalizedString
+ /**
+ * Lightning
+ */
+ lightning: () => LocalizedString
+ /**
+ * Dawn
+ */
+ dawn: () => LocalizedString
+ /**
+ * Validator
+ */
+ validator: () => LocalizedString
+ /**
+ * Validator (inactive)
+ */
+ inactive_validator: () => LocalizedString
+ }
+ field_errors: {
+ /**
+ * Required
+ */
+ required: () => LocalizedString
+ /**
+ * Invalid format
+ */
+ invalid_format: () => LocalizedString
+ /**
+ * Please enter a valid address
+ */
+ invalid_address: () => LocalizedString
+ /**
+ * The end date must be after the start date
+ */
+ end_before_start: () => LocalizedString
+ /**
+ * The end date must be in the future
+ */
+ end_before_today: () => LocalizedString
+ /**
+ * The start date must be in the future
+ */
+ start_after_today: () => LocalizedString
+ /**
+ * The end date must be within {days} days of the start date
+ */
+ end_after_max_duration: (arg: { days: string }) => LocalizedString
+ /**
+ * No voters found matching your search criteria.
+ */
+ failed_load_voters: () => LocalizedString
+ descriptions_errors: {
+ /**
+ * Please replace placeholder text with your own content before submitting the proposal.
+ */
+ placeholders_not_replaced: () => LocalizedString
+ /**
+ * Description cannot be empty. Please provide content for your proposal.
+ */
+ empty_description: () => LocalizedString
+ }
+ /**
+ * The Discourse topic does not exist or is not accessible
+ */
+ discourse_topic_not_exist: () => LocalizedString
+ }
+ voting_list: {
+ /**
+ * Voting options:
+ */
+ voting_options: () => LocalizedString
+ /**
+ * Select an option to vote:
+ */
+ option_to_vote: () => LocalizedString
+ /**
+ * Voting has not started yet
+ */
+ voting_has_not_started_yet: () => LocalizedString
+ /**
+ * Please connect your wallet
+ */
+ please_connect_your_wallet: () => LocalizedString
+ /**
+ * You have already voted
+ */
+ you_have_already_voted: () => LocalizedString
+ /**
+ * You don't have enough voting power
+ */
+ you_dont_have_enough_voting_power: () => LocalizedString
+ }
+ proposal: {
+ /**
+ * Proposal
+ */
+ title: () => LocalizedString
+ /**
+ * Proposed by
+ */
+ proposed_by: () => LocalizedString
+ /**
+ * Voting calendar
+ */
+ voting_calendar: () => LocalizedString
+ /**
+ * Confirm in your wallet...
+ */
+ confirm_in_your_wallet: () => LocalizedString
+ /**
+ * Who can vote
+ */
+ who_can_vote: () => LocalizedString
+ /**
+ * VeChain Foundation
+ */
+ vechain_foundation: () => LocalizedString
+ /**
+ * Node holders with voting power will be able to vote on this proposal.
+ */
+ node_holders: () => LocalizedString
+ /**
+ * Voting will start {date}
+ */
+ voting_will_start: (arg: { date: string }) => LocalizedString
+ /**
+ * See your vote details
+ */
+ see_your_vote: () => LocalizedString
+ /**
+ * See all ({voters}) voters
+ */
+ see_all_voters: (arg: { voters: number }) => LocalizedString
+ /**
+ * See first voter
+ */
+ see_first_voter: () => LocalizedString
+ /**
+ * Mark as executed
+ */
+ mark_as_executed: () => LocalizedString
+ /**
+ * Buy another node to increase your voting power on future proposals.
+ */
+ buy_another_node: () => LocalizedString
+ /**
+ * Voting is only possible for Node holders. Buy a node to vote on future proposals or increase your voting power.
+ */
+ buy_a_node: () => LocalizedString
+ vote_success: {
+ /**
+ * Vote submitted!
+ */
+ title: () => LocalizedString
+ /**
+ * Your vote was submitted successfully.
+ */
+ description: () => LocalizedString
+ }
+ cancel_proposal: {
+ /**
+ * Cancel proposal
+ */
+ title: () => LocalizedString
+ /**
+ * Canceling the proposal means it the voting will not take place and the proposal will not have no results.
+ */
+ description: () => LocalizedString
+ /**
+ * Reason
+ */
+ reason: () => LocalizedString
+ /**
+ * Write the reason for cancellation...
+ */
+ reason_placeholder: () => LocalizedString
+ /**
+ * Proposal canceled successfully
+ */
+ success_title: () => LocalizedString
+ /**
+ * The proposal has been canceled successfully. Voting will not take place.
+ */
+ success_description: () => LocalizedString
+ }
+ execute_proposal: {
+ /**
+ * Mark as Executed
+ */
+ title: () => LocalizedString
+ /**
+ * If the actions of the proposal have already been executed, you can mark this approved proposal as executed for the voters to know.
+ */
+ description: () => LocalizedString
+ /**
+ * Execution / Transaction details
+ */
+ label: () => LocalizedString
+ /**
+ * Insert link with the proof of the execution
+ */
+ link_placeholder: () => LocalizedString
+ }
+ delete_proposal: {
+ /**
+ * Delete proposal
+ */
+ title: () => LocalizedString
+ /**
+ * If you delete this draft, all the information of the proposal will not be possible to recover anymore.
+ */
+ description: () => LocalizedString
+ /**
+ * Are you sure you want to delete it?
+ */
+ confirmation: () => LocalizedString
+ /**
+ * No, go back
+ */
+ no_go_back: () => LocalizedString
+ /**
+ * Yes, Delete
+ */
+ yes_delete: () => LocalizedString
+ }
+ info_box: {
+ info: {
+ /**
+ * Minimum participation
+ */
+ title: () => LocalizedString
+ /**
+ * A minimum of {quorum}% participation must be reached to validate the voting of the proposal and get approval.
+ */
+ description: (arg: { quorum: number }) => LocalizedString
+ }
+ approved: {
+ /**
+ * Minimum participation reached
+ */
+ title: () => LocalizedString
+ /**
+ * The voting participation reached the minimum required of {quorum}% to get approval.
+ */
+ description: (arg: { quorum: number }) => LocalizedString
+ }
+ executed: {
+ /**
+ * Proposal Approved and Executed
+ */
+ title: () => LocalizedString
+ /**
+ * The voting approved the proposal and the actions have been executed.
+ */
+ description: () => LocalizedString
+ }
+ 'min-not-reached': {
+ /**
+ * Minimum participation not reached
+ */
+ title: () => LocalizedString
+ /**
+ * The voting participation didn’t reached the minimum required of {quorum}% to get approval.
+ */
+ description: (arg: { quorum: number }) => LocalizedString
+ }
+ rejected: {
+ /**
+ * Proposal Rejected
+ */
+ title: () => LocalizedString
+ /**
+ * The proposal didn’t get enough votes in favor to get approval.
+ */
+ description: () => LocalizedString
+ }
+ canceled: {
+ /**
+ * Proposal Canceled
+ */
+ title: () => LocalizedString
+ /**
+ * The proposal was canceled by VeChain or the proposer by the following reason:
+ */
+ description: () => LocalizedString
+ }
+ }
+ voting_power: {
+ /**
+ * Get more voting power
+ */
+ get_more_voting_power: () => LocalizedString
+ /**
+ * Get voting power
+ */
+ get_voting_power: () => LocalizedString
+ /**
+ * Voting power
+ */
+ title: () => LocalizedString
+ /**
+ * Your voting power was calculated at the time of the snapshot {snapshot}.
+ */
+ calculation: (arg: { snapshot: string }) => LocalizedString
+ /**
+ * Total voting power
+ */
+ total_voting_power: () => LocalizedString
+ warnings: {
+ /**
+ * The connected wallet has no voting power
+ */
+ zero_voting_power: () => LocalizedString
+ /**
+ * You have legacy nodes that haven’t been migrated yet. Migrate to get more voting power.
+ */
+ legacy_node: () => LocalizedString
+ delegated: {
+ /**
+ * Your voting power is delegated
+ */
+ title: () => LocalizedString
+ /**
+ * Your voting power is delegated to another node
+ */
+ description: () => LocalizedString
+ }
+ }
+ }
+ voters_table: {
+ filters: {
+ /**
+ * Search by address...
+ */
+ search_by_address: () => LocalizedString
+ /**
+ * Voting options
+ */
+ voting_options: () => LocalizedString
+ /**
+ * Node
+ */
+ node: () => LocalizedString
+ /**
+ * Sort by
+ */
+ sort_by: () => LocalizedString
+ }
+ header: {
+ /**
+ * Date
+ */
+ date: () => LocalizedString
+ /**
+ * Address
+ */
+ address: () => LocalizedString
+ /**
+ * Node
+ */
+ node: () => LocalizedString
+ /**
+ * Node ID
+ */
+ node_id: () => LocalizedString
+ /**
+ * Power
+ */
+ voting_power: () => LocalizedString
+ /**
+ * Option
+ */
+ voted_option: () => LocalizedString
+ /**
+ * Transaction ID
+ */
+ transaction_id: () => LocalizedString
+ }
+ }
+ create: {
+ /**
+ * Previewing proposal
+ */
+ previewing: () => LocalizedString
+ /**
+ * Create Proposal
+ */
+ title: () => LocalizedString
+ /**
+ * {current} of {total}
+ */
+ steps: (arg: { current: number, total: number }) => LocalizedString
+ /**
+ * Add the main details and setup the calendar
+ */
+ voting_details_desc: () => LocalizedString
+ /**
+ * Define the voting setup details
+ */
+ voting_setup_desc: () => LocalizedString
+ /**
+ * Review all the details before publishing
+ */
+ voting_summary_desc: () => LocalizedString
+ /**
+ * Add new option
+ */
+ add_new_option: () => LocalizedString
+ exit_proposal: {
+ /**
+ * Exit proposal creation
+ */
+ title: () => LocalizedString
+ /**
+ * By exiting you lose all the information written or you can save this proposal as a draft and finish later?
+ */
+ description: () => LocalizedString
+ /**
+ * You will lose all entered information if you exit now. Saving as a draft is only available on the final step of the form.
+ */
+ description_last_step: () => LocalizedString
+ /**
+ * Be aware that a draft proposal already exists. Saving now will overwrite your previous draft.
+ */
+ description_draft_exist: () => LocalizedString
+ /**
+ * Exit proposal
+ */
+ exit_button: () => LocalizedString
+ /**
+ * Save draft
+ */
+ save_button: () => LocalizedString
+ }
+ draft_saved: {
+ /**
+ * Draft saved!
+ */
+ title: () => LocalizedString
+ /**
+ * The proposal draft has been saved successfully and can now be continued later.
+ */
+ description: () => LocalizedString
+ }
+ details_form: {
+ /**
+ * Title
+ */
+ title: () => LocalizedString
+ /**
+ * What's the proposal title?
+ */
+ title_placeholder: () => LocalizedString
+ /**
+ * Description
+ */
+ description: () => LocalizedString
+ /**
+ * Add a description...
+ */
+ description_placeholder: () => LocalizedString
+ /**
+ * Header image
+ */
+ header_image: () => LocalizedString
+ /**
+ * Discourse url
+ */
+ discourse_url: () => LocalizedString
+ /**
+ * Discourse Topic
+ */
+ discourse_topic: () => LocalizedString
+ /**
+ * your-topic-name
+ */
+ discourse_topic_placeholder: () => LocalizedString
+ /**
+ * Enter the topic name from your VeChain Discourse discussion
+ */
+ discourse_topic_help: () => LocalizedString
+ /**
+ * Voting calendar
+ */
+ voting_calendar: () => LocalizedString
+ }
+ setup_form: {
+ /**
+ * Voting type
+ */
+ voting_type: () => LocalizedString
+ /**
+ * Select the type
+ */
+ voting_type_subtitle: () => LocalizedString
+ /**
+ * Voting Question
+ */
+ voting_question: () => LocalizedString
+ /**
+ * This question should provide exact context to the voting options:
+ */
+ voting_question_subtitle: () => LocalizedString
+ /**
+ * Write the question...
+ */
+ voting_question_placeholder: () => LocalizedString
+ /**
+ * Voting limit
+ */
+ voting_limit: () => LocalizedString
+ /**
+ * Define the minimum and maximum amount of options allowed per voter:
+ */
+ voting_limit_subtitle: () => LocalizedString
+ /**
+ * Voting options
+ */
+ voting_options: () => LocalizedString
+ /**
+ * The “single choice” voting type only allows the voter to select:
+ */
+ voting_choice_subtitle: () => LocalizedString
+ /**
+ * Add between 2 and 30 options to vote:
+ */
+ voting_options_subtitle: () => LocalizedString
+ /**
+ * Add new option
+ */
+ add_new_option: () => LocalizedString
+ /**
+ * Write the voting option...
+ */
+ voting_option_placeholder: () => LocalizedString
+ }
+ summary_form: {
+ main_details: {
+ /**
+ * Main details
+ */
+ title: () => LocalizedString
+ /**
+ * Calendar
+ */
+ calendar: () => LocalizedString
+ }
+ voting_setup: {
+ /**
+ * Voting setup
+ */
+ title: () => LocalizedString
+ /**
+ * Question
+ */
+ question: () => LocalizedString
+ /**
+ * Type
+ */
+ type: () => LocalizedString
+ /**
+ * Minimum {min} options - Maximum {limit} options
+ */
+ limit: (arg: { limit: number, min: number }) => LocalizedString
+ types: {
+ /**
+ * Single choice - For / Against / Abstain
+ */
+ SINGLE_CHOICE: () => LocalizedString
+ }
+ }
+ /**
+ * Publish Proposal
+ */
+ publish_proposal: () => LocalizedString
+ /**
+ * Please note that once the campaign is published, it can't be edited anymore.
+ */
+ publish_description: () => LocalizedString
+ /**
+ * Are you sure you want to publish this proposal?
+ */
+ publish_sub_description: () => LocalizedString
+ /**
+ * Publishing failed
+ */
+ publish_failed: () => LocalizedString
+ /**
+ * The publishing of the proposal couldn’t be completed. Please try again.
+ */
+ publish_failed_description: () => LocalizedString
+ /**
+ * Proposal published
+ */
+ publish_success: () => LocalizedString
+ /**
+ * The proposal has been successfully publish and can now be seen publicly.
+ */
+ publish_success_description: () => LocalizedString
+ }
+ }
+ /**
+ * Go to StarGate
+ */
+ go_to_stargate: () => LocalizedString
+ /**
+ * Proposal not found
+ */
+ proposal_not_found: () => LocalizedString
+ /**
+ * The proposal you're looking for doesn't exist or may have been removed. It's possible the URL is incorrect or the proposal has been deleted.
+ */
+ proposal_not_found_description: () => LocalizedString
+ /**
+ * Back to Proposals
+ */
+ back_to_proposals: () => LocalizedString
+ /**
+ * Try Again
+ */
+ try_again: () => LocalizedString
+ /**
+ * Starts in
+ */
+ starts_in: () => LocalizedString
+ /**
+ * Ends in
+ */
+ ends_in: () => LocalizedString
+ /**
+ * Timeline
+ */
+ timeline: () => LocalizedString
+ /**
+ * Created
+ */
+ timeline_created: () => LocalizedString
+ /**
+ * Proposal Canceled
+ */
+ proposal_canceled: () => LocalizedString
+ /**
+ * The proposal was canceled by VeChain or the proposer by the following reason:
+ */
+ proposal_canceled_description: () => LocalizedString
+ /**
+ * No reason provided
+ */
+ no_reason_provided: () => LocalizedString
+ /**
+ * Unknown error
+ */
+ unknown_error: () => LocalizedString
+ /**
+ * Failed to execute proposal
+ */
+ failed_to_execute_proposal: () => LocalizedString
+ /**
+ * Proposal Approved and Executed
+ */
+ proposal_approved_and_executed: () => LocalizedString
+ /**
+ * The voting participation reached the minimum quorum to get approval.
+ */
+ proposal_approved: () => LocalizedString
+ /**
+ * The voting approved the proposal and the actions have been executed.
+ */
+ the_voting_approved_the_proposal_and_the_actions_have_been_executed: () => LocalizedString
+ /**
+ * See details
+ */
+ see_details: () => LocalizedString
+ /**
+ * Proposal Rejected
+ */
+ proposal_rejected: () => LocalizedString
+ /**
+ * The proposal didn't get enough votes in favor to get approval.
+ */
+ the_proposal_didnt_get_enough_votes_in_favor_to_get_approval: () => LocalizedString
+ /**
+ * Minimum participant not met
+ */
+ minimum_quorum_not_reached: () => LocalizedString
+ /**
+ * Quorum of {quorum} voting power not reached.
+ */
+ quorum_not_reached: (arg: { quorum: string }) => LocalizedString
+ /**
+ * Quorum of {quorum} voting power not reached yet.
+ */
+ quorum_not_reached_yet: (arg: { quorum: string }) => LocalizedString
+ /**
+ * Quorum reached.
+ */
+ quorum_reached: () => LocalizedString
+ /**
+ * Vote submission failed
+ */
+ vote_submission_failed: () => LocalizedString
+ /**
+ * Vote submitted successfully!
+ */
+ vote_submitted_successfully: () => LocalizedString
+ /**
+ * Submit your vote
+ */
+ submit_your_vote: () => LocalizedString
+ /**
+ * Your vote cannot be changed once submitted.
+ */
+ vote_cannot_be_changed: () => LocalizedString
+ /**
+ * Waiting wallet confirmation...
+ */
+ waiting_wallet_confirmation: () => LocalizedString
+ /**
+ * Confirm vote
+ */
+ confirm_vote: () => LocalizedString
+ /**
+ * You voted
+ */
+ you_voted: () => LocalizedString
+ }
+ proposals: {
+ /**
+ * Proposals
+ */
+ title: () => LocalizedString
+ /**
+ * Create Proposal
+ */
+ create: () => LocalizedString
+ /**
+ * Search proposals...
+ */
+ search_placeholder: () => LocalizedString
+ /**
+ * No proposals found
+ */
+ no_proposals: () => LocalizedString
+ /**
+ * {current} of {total} proposals
+ */
+ pagination: (arg: { current: number, total: number }) => LocalizedString
+ }
+ statuses: {
+ /**
+ * Voted
+ */
+ voted: () => LocalizedString
+ }
+ badge: {
+ /**
+ * Draft
+ */
+ draft: () => LocalizedString
+ /**
+ * Upcoming
+ */
+ upcoming: () => LocalizedString
+ /**
+ * Voting now
+ */
+ voting: () => LocalizedString
+ /**
+ * Approved
+ */
+ approved: () => LocalizedString
+ /**
+ * Executed
+ */
+ executed: () => LocalizedString
+ /**
+ * Canceled
+ */
+ canceled: () => LocalizedString
+ /**
+ * Rejected
+ */
+ rejected: () => LocalizedString
+ /**
+ * Quorum not reached
+ */
+ 'min-not-reached': () => LocalizedString
+ }
+ filters: {
+ /**
+ * Filters
+ */
+ title: () => LocalizedString
+ /**
+ * Apply
+ */
+ apply: () => LocalizedString
+ /**
+ * Reset
+ */
+ reset: () => LocalizedString
+ /**
+ * Select all
+ */
+ select_all: () => LocalizedString
+ /**
+ * Deselect all
+ */
+ deselect_all: () => LocalizedString
+ sort: {
+ /**
+ * Newest
+ */
+ newest: () => LocalizedString
+ /**
+ * Oldest
+ */
+ oldest: () => LocalizedString
+ /**
+ * Most participant
+ */
+ most_participant: () => LocalizedString
+ /**
+ * Least participant
+ */
+ least_participant: () => LocalizedString
+ }
+ }
+ header: {
+ /**
+ * VeChainThor Voting Platform
+ */
+ title: () => LocalizedString
+ /**
+ * Vote to shape the future of VeChainThor
+ */
+ description: () => LocalizedString
+ /**
+ * How to vote
+ */
+ how_to_vote: () => LocalizedString
+ /**
+ * Get voting power
+ */
+ how_to_get_voting_power: () => LocalizedString
+ }
+ stargate_warning: {
+ /**
+ * StarGate Node Migration Required
+ */
+ title: () => LocalizedString
+ /**
+ * You have 1 or more non-migrated nodes. Please migrate them as soon as possible to continue voting.
+ */
+ description: () => LocalizedString
+ /**
+ * https://app.stargate.vechain.org/
+ */
+ migration_link: () => LocalizedString
+ /**
+ * If a proposal has already started, you will not be able to vote on it even after migration. Only future proposals will be available for voting.
+ */
+ ongoing_proposal_warning: () => LocalizedString
+ /**
+ * If you want to continue anyway, write this text: agree-with-this
+ */
+ confirmation_instruction: () => LocalizedString
+ /**
+ * Please type exactly 'agree-with-this' to continue
+ */
+ confirmation_error: () => LocalizedString
+ }
+ footer: {
+ /**
+ * v1.0.0
+ */
+ version: () => LocalizedString
+ /**
+ * All Rights Reserved © Vechain Foundation San Marino S.r.l.
+ */
+ all_right: () => LocalizedString
+ legal: {
+ /**
+ * Legal
+ */
+ title: () => LocalizedString
+ /**
+ * Terms of Service
+ */
+ terms_of_service: () => LocalizedString
+ /**
+ * Privacy Policy
+ */
+ privacy_policy: () => LocalizedString
+ /**
+ * Cookies Policy
+ */
+ cookies_policy: () => LocalizedString
+ }
+ resources: {
+ /**
+ * Resources
+ */
+ title: () => LocalizedString
+ /**
+ * Docs
+ */
+ docs: () => LocalizedString
+ /**
+ * StarGate
+ */
+ stargate: () => LocalizedString
+ /**
+ * Support
+ */
+ support: () => LocalizedString
+ /**
+ * Governance Charter
+ */
+ governance_charter: () => LocalizedString
+ }
+ }
+ admin: {
+ /**
+ * Admin Dashboard
+ */
+ title: () => LocalizedString
+ tabs: {
+ /**
+ * Contracts
+ */
+ contracts: () => LocalizedString
+ /**
+ * Utils
+ */
+ utils: () => LocalizedString
+ /**
+ * Users
+ */
+ users: () => LocalizedString
+ /**
+ * Governance Settings
+ */
+ governance_settings: () => LocalizedString
+ /**
+ * Voting Power Query
+ */
+ voting_power_timepoint: () => LocalizedString
+ }
+ contracts: {
+ /**
+ * VeVote
+ */
+ vevote: () => LocalizedString
+ /**
+ * Node Management
+ */
+ node_management: () => LocalizedString
+ /**
+ * Stargate Nodes
+ */
+ stargate_nodes: () => LocalizedString
+ }
+ vevote_contract: {
+ /**
+ * Contract Address:
+ */
+ contract_address: () => LocalizedString
+ /**
+ * VeVote Contract Information
+ */
+ title: () => LocalizedString
+ /**
+ * Loading VeVote Contract Information...
+ */
+ loading: () => LocalizedString
+ /**
+ * Error loading VeVote contract data: {error}
+ */
+ error: (arg: { error: string }) => LocalizedString
+ /**
+ * No VeVote contract data available
+ */
+ no_data: () => LocalizedString
+ /**
+ * Contract Version
+ */
+ contract_version: () => LocalizedString
+ /**
+ * Quorum Numerator
+ */
+ quorum_numerator: () => LocalizedString
+ /**
+ * Quorum Denominator
+ */
+ quorum_denominator: () => LocalizedString
+ /**
+ * Min Voting Delay
+ */
+ min_voting_delay: () => LocalizedString
+ /**
+ * Min Voting Duration
+ */
+ min_voting_duration: () => LocalizedString
+ /**
+ * Max Voting Duration
+ */
+ max_voting_duration: () => LocalizedString
+ /**
+ * Min Staked Amount
+ */
+ min_staked_amount: () => LocalizedString
+ /**
+ * {percentage}% required
+ */
+ quorum_percentage: (arg: { percentage: number }) => LocalizedString
+ /**
+ * Contract Information
+ */
+ contract_info_title: () => LocalizedString
+ }
+ node_management: {
+ /**
+ * Node Management Contract Information
+ */
+ title: () => LocalizedString
+ /**
+ * Enter a wallet address to view detailed node ownership and delegation information for that account.
+ */
+ help_text: () => LocalizedString
+ /**
+ * User Address
+ */
+ user_address_label: () => LocalizedString
+ /**
+ * Enter user address (0x...)
+ */
+ user_address_placeholder: () => LocalizedString
+ /**
+ * Load User Node Info
+ */
+ load_button: () => LocalizedString
+ /**
+ * Loading...
+ */
+ loading_button: () => LocalizedString
+ /**
+ * Loading user node information...
+ */
+ loading_text: () => LocalizedString
+ /**
+ * Node Information for {address}
+ */
+ node_info_title: (arg: { address: string }) => LocalizedString
+ /**
+ * Is Node Holder
+ */
+ is_node_holder: () => LocalizedString
+ /**
+ * Is Node Delegator
+ */
+ is_node_delegator: () => LocalizedString
+ /**
+ * Owned Nodes
+ */
+ owned_nodes: () => LocalizedString
+ /**
+ * Managed Nodes
+ */
+ managed_nodes: () => LocalizedString
+ /**
+ * IDs: {ids}
+ */
+ ids_label: (arg: { ids: string }) => LocalizedString
+ /**
+ * Yes
+ */
+ yes: () => LocalizedString
+ /**
+ * No
+ */
+ no: () => LocalizedString
+ /**
+ * Error loading node data: {error}
+ */
+ error: (arg: { error: string }) => LocalizedString
+ /**
+ * Node Information
+ */
+ card_title: () => LocalizedString
+ /**
+ * Results for {address}
+ */
+ results_for: (arg: { address: string }) => LocalizedString
+ /**
+ * No node information available for this address
+ */
+ no_results: () => LocalizedString
+ /**
+ * Available Methods
+ */
+ methods_title: () => LocalizedString
+ /**
+ * This component demonstrates the NodeManagementService functionality. You can extend it to show additional statistics like total nodes, delegation stats, etc.
+ */
+ methods_description: () => LocalizedString
+ }
+ stargate_nodes: {
+ /**
+ * Stargate NFT Contract Information
+ */
+ title: () => LocalizedString
+ /**
+ * Loading Stargate NFT Information...
+ */
+ loading: () => LocalizedString
+ /**
+ * Error loading Stargate NFT data: {error}
+ */
+ error: (arg: { error: string }) => LocalizedString
+ /**
+ * No Stargate NFT data available
+ */
+ no_data: () => LocalizedString
+ /**
+ * Total Supply
+ */
+ total_supply: () => LocalizedString
+ /**
+ * Available Levels
+ */
+ available_levels: () => LocalizedString
+ /**
+ * Level IDs: {ids}
+ */
+ level_ids: (arg: { ids: string }) => LocalizedString
+ /**
+ * Level Details
+ */
+ level_details_title: () => LocalizedString
+ table: {
+ /**
+ * Level
+ */
+ level: () => LocalizedString
+ /**
+ * Name
+ */
+ name: () => LocalizedString
+ /**
+ * Is X-Node
+ */
+ is_x_node: () => LocalizedString
+ /**
+ * Maturity Blocks
+ */
+ maturity_blocks: () => LocalizedString
+ /**
+ * VET Required
+ */
+ vet_required: () => LocalizedString
+ /**
+ * Circulating
+ */
+ circulating: () => LocalizedString
+ /**
+ * Cap
+ */
+ cap: () => LocalizedString
+ }
+ /**
+ * Yes
+ */
+ yes: () => LocalizedString
+ /**
+ * No
+ */
+ no: () => LocalizedString
+ /**
+ * N/A
+ */
+ not_available: () => LocalizedString
+ /**
+ * Contract Information
+ */
+ contract_info_title: () => LocalizedString
+ /**
+ * This displays comprehensive information about the Stargate NFT contract including level configurations, supply information, and staking requirements.
+ */
+ contract_description: () => LocalizedString
+ }
+ /**
+ * {number}s
+ */
+ format_seconds: (arg: { number: number }) => LocalizedString
+ /**
+ * {minutes} min ({seconds}s)
+ */
+ format_minutes_seconds: (arg: { minutes: number, seconds: number }) => LocalizedString
+ /**
+ * {number} days
+ */
+ format_days: (arg: { number: number }) => LocalizedString
+ /**
+ * {amount} VET
+ */
+ vet_format: (arg: { amount: string }) => LocalizedString
+ governance_settings: {
+ /**
+ * Governance Settings
+ */
+ title: () => LocalizedString
+ /**
+ * Configure VeVote governance parameters
+ */
+ description: () => LocalizedString
+ /**
+ * Quorum Numerator
+ */
+ quorum_numerator_label: () => LocalizedString
+ /**
+ * Required votes numerator (current: {current})
+ */
+ quorum_numerator_help: (arg: { current: number }) => LocalizedString
+ /**
+ * Min Voting Delay
+ */
+ min_voting_delay_label: () => LocalizedString
+ /**
+ * Minimum delay before voting starts (in blocks)
+ */
+ min_voting_delay_help: () => LocalizedString
+ /**
+ * Min Voting Duration
+ */
+ min_voting_duration_label: () => LocalizedString
+ /**
+ * Minimum voting duration (in blocks)
+ */
+ min_voting_duration_help: () => LocalizedString
+ /**
+ * Max Voting Duration
+ */
+ max_voting_duration_label: () => LocalizedString
+ /**
+ * Maximum voting duration (in blocks)
+ */
+ max_voting_duration_help: () => LocalizedString
+ /**
+ * Min Staked VET Amount
+ */
+ min_staked_vet_amount_label: () => LocalizedString
+ /**
+ * Minimum VET amount required to stake
+ */
+ min_staked_vet_amount_help: () => LocalizedString
+ /**
+ * Level ID Multipliers
+ */
+ level_multipliers_label: () => LocalizedString
+ /**
+ * Voting weight multipliers for each level ID (scaled by 100)
+ */
+ level_multipliers_help: () => LocalizedString
+ /**
+ * Validator
+ */
+ validator_multiplier: () => LocalizedString
+ /**
+ * Strength
+ */
+ strength: () => LocalizedString
+ /**
+ * Thunder
+ */
+ thunder: () => LocalizedString
+ /**
+ * Mjolnir
+ */
+ mjolnir: () => LocalizedString
+ /**
+ * VeThor X
+ */
+ vethor_x: () => LocalizedString
+ /**
+ * Strength X
+ */
+ strength_x: () => LocalizedString
+ /**
+ * Thunder X
+ */
+ thunder_x: () => LocalizedString
+ /**
+ * Mjolnir X
+ */
+ mjolnir_x: () => LocalizedString
+ /**
+ * Dawn Node
+ */
+ dawn: () => LocalizedString
+ /**
+ * Lightning Node
+ */
+ lightning: () => LocalizedString
+ /**
+ * Flash Node
+ */
+ flash: () => LocalizedString
+ /**
+ * Level ID
+ */
+ level_id: () => LocalizedString
+ /**
+ * Node Name
+ */
+ node_name: () => LocalizedString
+ /**
+ * Current
+ */
+ current_multiplier: () => LocalizedString
+ /**
+ * New Value
+ */
+ new_multiplier: () => LocalizedString
+ /**
+ * Update Settings
+ */
+ update_settings: () => LocalizedString
+ /**
+ * Update Governance Settings
+ */
+ update_governance_settings: () => LocalizedString
+ /**
+ * Update Multipliers
+ */
+ update_multipliers: () => LocalizedString
+ /**
+ * Updating...
+ */
+ updating: () => LocalizedString
+ /**
+ * Settings Updated Successfully
+ */
+ success_title: () => LocalizedString
+ /**
+ * Governance settings have been updated and are now active.
+ */
+ success_description: () => LocalizedString
+ /**
+ * Failed to Update Settings
+ */
+ error_title: () => LocalizedString
+ /**
+ * There was an error updating the governance settings: {error}
+ */
+ error_description: (arg: { error: string }) => LocalizedString
+ /**
+ * Value must be between {min} and {max}
+ */
+ invalid_range: (arg: { max: number, min: number }) => LocalizedString
+ /**
+ * This field is required
+ */
+ required_field: () => LocalizedString
+ /**
+ * Current: {value}
+ */
+ current_value: (arg: { value: string }) => LocalizedString
+ }
+ user_management: {
+ /**
+ * User Management
+ */
+ title: () => LocalizedString
+ /**
+ * Grant or revoke roles for VeVote governance
+ */
+ description: () => LocalizedString
+ /**
+ * User Address
+ */
+ user_address_label: () => LocalizedString
+ /**
+ * Enter user address (0x...)
+ */
+ user_address_placeholder: () => LocalizedString
+ /**
+ * Role
+ */
+ role_label: () => LocalizedString
+ /**
+ * Select a role
+ */
+ role_placeholder: () => LocalizedString
+ /**
+ * Current Roles:
+ */
+ current_roles_label: () => LocalizedString
+ /**
+ * Checking roles...
+ */
+ checking_roles: () => LocalizedString
+ /**
+ * No roles assigned
+ */
+ no_roles_assigned: () => LocalizedString
+ }
+ role_users: {
+ /**
+ * Role Users
+ */
+ title: () => LocalizedString
+ /**
+ * Select a role to view all users who have been granted that specific role.
+ */
+ help_text: () => LocalizedString
+ /**
+ * Role
+ */
+ role_label: () => LocalizedString
+ /**
+ * Select a role to query
+ */
+ role_placeholder: () => LocalizedString
+ /**
+ * Query Role Users
+ */
+ query_button: () => LocalizedString
+ /**
+ * Loading...
+ */
+ loading: () => LocalizedString
+ /**
+ * Fetching users with selected role...
+ */
+ loading_text: () => LocalizedString
+ /**
+ * Users with Role
+ */
+ results_title: () => LocalizedString
+ /**
+ * {count} {count|{1: user, *: users}}
+ */
+ user_count: (arg: { count: number | '1' | string }) => LocalizedString
+ /**
+ * Role: {role}
+ */
+ role_selected: (arg: { role: string }) => LocalizedString
+ /**
+ * Granted: {date}
+ */
+ granted_at: (arg: { date: string }) => LocalizedString
+ /**
+ * View TX
+ */
+ view_tx: () => LocalizedString
+ /**
+ * No users found with this role
+ */
+ no_users: () => LocalizedString
+ /**
+ * Scroll to see more users
+ */
+ scrollable_hint: () => LocalizedString
+ /**
+ * Error fetching role users: {error}
+ */
+ error_description: (arg: { error: string }) => LocalizedString
+ }
+ user_role_checker: {
+ /**
+ * Your Permissions
+ */
+ title: () => LocalizedString
+ /**
+ * Connect wallet to see your roles
+ */
+ connect_wallet_message: () => LocalizedString
+ /**
+ * Checking your roles...
+ */
+ checking_roles: () => LocalizedString
+ }
+ voting_power_timepoint: {
+ /**
+ * Address:
+ */
+ address: () => LocalizedString
+ /**
+ * Timepoint:
+ */
+ timepoint: () => LocalizedString
+ /**
+ * Master Address:
+ */
+ master_address: () => LocalizedString
+ /**
+ * Voting Power at Timepoint
+ */
+ title: () => LocalizedString
+ /**
+ * Query historical voting power for any wallet at a specific timepoint
+ */
+ description: () => LocalizedString
+ /**
+ * Wallet Address
+ */
+ address_label: () => LocalizedString
+ /**
+ * Enter wallet address (0x...)
+ */
+ address_placeholder: () => LocalizedString
+ /**
+ * Timepoint
+ */
+ timepoint_label: () => LocalizedString
+ /**
+ * Enter timepoint
+ */
+ timepoint_placeholder: () => LocalizedString
+ /**
+ * Master Address
+ */
+ master_address_label: () => LocalizedString
+ /**
+ * Enter master address for validator power (0x...)
+ */
+ master_address_placeholder: () => LocalizedString
+ /**
+ * Query Voting Power
+ */
+ query_button: () => LocalizedString
+ /**
+ * Querying...
+ */
+ querying: () => LocalizedString
+ /**
+ * Voting Power Results
+ */
+ results_title: () => LocalizedString
+ /**
+ * Node-based Power:
+ */
+ node_power_label: () => LocalizedString
+ /**
+ * Validator Power:
+ */
+ validator_power_label: () => LocalizedString
+ /**
+ * Total Power:
+ */
+ total_power_label: () => LocalizedString
+ /**
+ * No results to display
+ */
+ no_results: () => LocalizedString
+ /**
+ * Please enter a valid address
+ */
+ invalid_address: () => LocalizedString
+ /**
+ * Please enter a valid timepoint/block number
+ */
+ invalid_timepoint: () => LocalizedString
+ /**
+ * Wallet address is required
+ */
+ address_required: () => LocalizedString
+ /**
+ * Timepoint is required
+ */
+ timepoint_required: () => LocalizedString
+ /**
+ * Query Failed
+ */
+ error_title: () => LocalizedString
+ /**
+ * There was an error querying voting power: {error}
+ */
+ error_description: (arg: { error: string }) => LocalizedString
+ /**
+ * Enter a wallet address and timepoint to view historical voting power. Optionally provide a master address to check validator-delegated power.
+ */
+ help_text: () => LocalizedString
+ }
+ /**
+ * Unknown error
+ */
+ unknown_error: () => LocalizedString
+ /**
+ * This operation is sensitive. Please use with caution.
+ */
+ sensitive_operation_warning: () => LocalizedString
+ /**
+ * Your Permissions
+ */
+ your_permissions: () => LocalizedString
+ common_roles: {
+ /**
+ * Default Admin
+ */
+ DEFAULT_ADMIN_ROLE: () => LocalizedString
+ /**
+ * Executor
+ */
+ EXECUTOR_ROLE: () => LocalizedString
+ /**
+ * Settings Manager
+ */
+ SETTINGS_MANAGER_ROLE: () => LocalizedString
+ /**
+ * Node Weight Manager
+ */
+ NODE_WEIGHT_MANAGER_ROLE: () => LocalizedString
+ /**
+ * Upgrader
+ */
+ UPGRADER_ROLE: () => LocalizedString
+ /**
+ * Whitelisted
+ */
+ WHITELISTED_ROLE: () => LocalizedString
+ /**
+ * Whitelist Admin
+ */
+ WHITELIST_ADMIN_ROLE: () => LocalizedString
+ /**
+ * Pauser
+ */
+ PAUSER_ROLE: () => LocalizedString
+ /**
+ * Level Operator
+ */
+ LEVEL_OPERATOR_ROLE: () => LocalizedString
+ /**
+ * Manager
+ */
+ MANAGER_ROLE: () => LocalizedString
+ /**
+ * Whitelister
+ */
+ WHITELISTER_ROLE: () => LocalizedString
+ /**
+ * Grant Role
+ */
+ grant_role: () => LocalizedString
+ /**
+ * Revoke Role
+ */
+ revoke_role: () => LocalizedString
+ /**
+ * Granting...
+ */
+ granting: () => LocalizedString
+ /**
+ * Revoking...
+ */
+ revoking: () => LocalizedString
+ /**
+ * Role Granted Successfully
+ */
+ grant_success_title: () => LocalizedString
+ /**
+ * The role has been granted to the user.
+ */
+ grant_success_description: () => LocalizedString
+ /**
+ * Role Revoked Successfully
+ */
+ revoke_success_title: () => LocalizedString
+ /**
+ * The role has been revoked from the user.
+ */
+ revoke_success_description: () => LocalizedString
+ /**
+ * Role Operation Failed
+ */
+ error_title: () => LocalizedString
+ /**
+ * There was an error with the role operation: {error}
+ */
+ error_description: (arg: { error: string }) => LocalizedString
+ /**
+ * Please enter a valid address
+ */
+ invalid_address: () => LocalizedString
+ /**
+ * Please select a role
+ */
+ role_required: () => LocalizedString
+ /**
+ * User address is required
+ */
+ address_required: () => LocalizedString
+ }
+ }
+}
-export type Formatters = {};
+export type Formatters = {}
diff --git a/apps/frontend/src/icons/Menu.tsx b/apps/frontend/src/icons/Menu.tsx
new file mode 100644
index 00000000..5abd676c
--- /dev/null
+++ b/apps/frontend/src/icons/Menu.tsx
@@ -0,0 +1,20 @@
+import { SVGProps } from "react";
+
+export const Menu = (props: SVGProps) => {
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/apps/frontend/src/icons/index.ts b/apps/frontend/src/icons/index.ts
index 14112e7c..0110e14a 100644
--- a/apps/frontend/src/icons/index.ts
+++ b/apps/frontend/src/icons/index.ts
@@ -49,6 +49,7 @@ import { Logout } from "./Logout";
import { Node } from "./Node";
import { MessageSquare } from "./MessageSquare";
import { Discourse } from "./Discourse";
+import { Menu } from "./Menu";
export * from "./ThumbsUpIcon";
export * from "./ThumbsDownIcon";
export * from "./AbstainIcon";
@@ -105,4 +106,5 @@ export {
Logout as LogoutIcon,
Node as NodeIcon,
Discourse as DiscourseIcon,
+ Menu as MenuIcon,
};
diff --git a/apps/frontend/src/pages/Admin/AdminDashboard.tsx b/apps/frontend/src/pages/Admin/AdminDashboard.tsx
new file mode 100644
index 00000000..7308ef82
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/AdminDashboard.tsx
@@ -0,0 +1,29 @@
+import { useBreakpointValue } from "@chakra-ui/react";
+import { VeVoteContract } from "./components/Contracts/VeVoteContract";
+import { NodeManagement } from "./components/Contracts/NodeManagement";
+import { StargateNodes } from "./components/Contracts/StargateNodes";
+import { UserManagement } from "./components/Utils/UserManagement";
+import { GovernanceSettings } from "./components/Utils/GovernanceSettings";
+import { ResponsiveNavigation } from "./components/navigation/ResponsiveNavigation";
+import { PageContainer } from "@/components/PageContainer";
+
+export function AdminDashboard() {
+ const containerPadding = useBreakpointValue({
+ base: 4,
+ md: 8,
+ });
+
+ const contractsContent = [
+ ,
+ ,
+ ,
+ ];
+
+ const utilsContent = [, ];
+
+ return (
+
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/Contracts/NodeManagement.tsx b/apps/frontend/src/pages/Admin/components/Contracts/NodeManagement.tsx
new file mode 100644
index 00000000..dd25933b
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Contracts/NodeManagement.tsx
@@ -0,0 +1,54 @@
+import { Box, Heading, Text, HStack, VStack, useBreakpointValue } from "@chakra-ui/react";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { CopyLink } from "@/components/ui/CopyLink";
+import { getConfig } from "@repo/config";
+import { formatAddress } from "@/utils/address";
+import { NodeManagementContractInfo } from "./components/NodeManagementContractInfo";
+import { useMemo } from "react";
+import { useContractAddresses } from "../../hooks/useContractAddresses";
+
+const EXPLORER_URL = getConfig(import.meta.env.VITE_APP_ENV).network.explorerUrl;
+
+export function NodeManagement() {
+ const { LL } = useI18nContext();
+
+ const { contractAddresses } = useContractAddresses();
+
+ const nodeManagementContractAddress = useMemo(
+ () => contractAddresses?.nodeManagementAddress,
+ [contractAddresses?.nodeManagementAddress],
+ );
+ const stackSpacing = useBreakpointValue({
+ base: 4,
+ md: 6,
+ });
+
+ return (
+
+
+
+
+ {LL.admin.node_management.title()}
+
+ {nodeManagementContractAddress && (
+
+ {LL.admin.vevote_contract.contract_address()}
+
+ {formatAddress(nodeManagementContractAddress)}
+
+
+ )}
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/Contracts/StargateNodes.tsx b/apps/frontend/src/pages/Admin/components/Contracts/StargateNodes.tsx
new file mode 100644
index 00000000..ac518d27
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Contracts/StargateNodes.tsx
@@ -0,0 +1,77 @@
+import { CopyLink } from "@/components/ui/CopyLink";
+import { GenericInfoBox } from "@/components/ui/GenericInfoBox";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { formatAddress } from "@/utils/address";
+import { Box, Heading, HStack, Spinner, Text, useBreakpointValue, VStack } from "@chakra-ui/react";
+import { getConfig } from "@repo/config";
+import { useMemo } from "react";
+import { useStargateStats } from "../../hooks";
+import { useContractAddresses } from "../../hooks/useContractAddresses";
+import { StargateContractInfo } from "./components/StargateContractInfo";
+import { StargateLevelDetails } from "./components/StargateLevelDetails";
+
+const EXPLORER_URL = getConfig(import.meta.env.VITE_APP_ENV).network.explorerUrl;
+
+export function StargateNodes() {
+ const { LL } = useI18nContext();
+ const { data: stargateStats, isLoading, error } = useStargateStats();
+ const { contractAddresses } = useContractAddresses();
+
+ const stargateNFTContractAddress = useMemo(
+ () => contractAddresses?.stargateAddress,
+ [contractAddresses?.stargateAddress],
+ );
+
+ const stackSpacing = useBreakpointValue({
+ base: 4,
+ md: 6,
+ });
+
+ if (isLoading) {
+ return (
+
+
+ {LL.admin.stargate_nodes.loading()}
+
+ );
+ }
+
+ if (error || !stargateStats) {
+ return (
+
+ {LL.admin.stargate_nodes.no_data()}
+
+ );
+ }
+
+ return (
+
+
+
+
+ {LL.admin.stargate_nodes.title()}
+
+ {stargateNFTContractAddress && (
+
+ {LL.admin.vevote_contract.contract_address()}
+
+ {formatAddress(stargateNFTContractAddress)}
+
+
+ )}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/Contracts/VeVoteContract.tsx b/apps/frontend/src/pages/Admin/components/Contracts/VeVoteContract.tsx
new file mode 100644
index 00000000..e3655bf7
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Contracts/VeVoteContract.tsx
@@ -0,0 +1,67 @@
+import { CopyLink } from "@/components/ui/CopyLink";
+import { GenericInfoBox } from "@/components/ui/GenericInfoBox";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { formatAddress } from "@/utils/address";
+import { Box, Heading, HStack, Spinner, Text, useBreakpointValue, VStack } from "@chakra-ui/react";
+import { getConfig } from "@repo/config";
+import { useVeVoteInfo } from "../../hooks";
+import { UserRoleChecker } from "../common/UserRoleChecker";
+import { VevoteContractInfo } from "./components/VevoteContractInfo";
+
+const EXPLORER_URL = getConfig(import.meta.env.VITE_APP_ENV).network.explorerUrl;
+const contractAddress = getConfig(import.meta.env.VITE_APP_ENV).vevoteContractAddress;
+
+export function VeVoteContract() {
+ const { LL } = useI18nContext();
+ const { data: veVoteInfo, isLoading, error } = useVeVoteInfo();
+
+ const stackSpacing = useBreakpointValue({
+ base: 4,
+ md: 6,
+ });
+
+ if (isLoading) {
+ return (
+
+
+ {LL.admin.vevote_contract.loading()}
+
+ );
+ }
+
+ if (error || !veVoteInfo) {
+ return (
+
+ {LL.admin.vevote_contract.no_data()}
+
+ );
+ }
+
+ return (
+
+
+
+
+ {LL.admin.vevote_contract.title()}
+
+
+ {LL.admin.vevote_contract.contract_address()}
+
+ {formatAddress(contractAddress)}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/Contracts/components/NodeManagementContractInfo.tsx b/apps/frontend/src/pages/Admin/components/Contracts/components/NodeManagementContractInfo.tsx
new file mode 100644
index 00000000..c36d5c5e
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Contracts/components/NodeManagementContractInfo.tsx
@@ -0,0 +1,152 @@
+import {
+ VStack,
+ HStack,
+ Text,
+ Input,
+ Button,
+ FormControl,
+ Spinner,
+ Divider,
+} from "@chakra-ui/react";
+import { useMemo, useState } from "react";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { useUserNodeInfo } from "../../../hooks";
+import { AdminCard } from "../../common/AdminCard";
+import { FormSkeleton } from "@/components/ui/FormSkeleton";
+import { formatAddress } from "@/utils/address";
+import { nodeInfoSchema, type NodeInfoSchema } from "@/schema/adminSchema";
+import { Label } from "@/components/ui/Label";
+import { InputMessage } from "@/components/ui/InputMessage";
+import { GenericInfoBox } from "@/components/ui/GenericInfoBox";
+
+export function NodeManagementContractInfo() {
+ const { LL } = useI18nContext();
+ const [searchAddress, setSearchAddress] = useState("");
+
+ const { data: userNodeInfo, isLoading, error } = useUserNodeInfo(searchAddress);
+
+ const handleFormSubmit = async (values: NodeInfoSchema) => {
+ setSearchAddress(values.userAddress.trim());
+ };
+
+ const nodeData = useMemo(() => {
+ if (!userNodeInfo) return [];
+
+ const ownedNodes =
+ userNodeInfo.ownedNodes.length > 0 ? ` (${userNodeInfo.ownedNodes.map(id => id.toString()).join(", ")})` : "";
+ const managedNodes =
+ userNodeInfo.managedNodes.length > 0 ? ` (${userNodeInfo.managedNodes.map(id => id.toString()).join(", ")})` : "";
+ return [
+ {
+ label: LL.admin.node_management.is_node_holder(),
+ value: userNodeInfo.isNodeHolder ? LL.admin.node_management.yes() : LL.admin.node_management.no(),
+ },
+ {
+ label: LL.admin.node_management.is_node_delegator(),
+ value: userNodeInfo.isNodeDelegator ? LL.admin.node_management.yes() : LL.admin.node_management.no(),
+ },
+ {
+ label: LL.admin.node_management.owned_nodes(),
+ value: `${userNodeInfo.ownedNodes.length}${ownedNodes}`,
+ },
+ {
+ label: LL.admin.node_management.managed_nodes(),
+ value: `${userNodeInfo.managedNodes.length}${managedNodes}`,
+ },
+ ];
+ }, [userNodeInfo, LL]);
+
+ return (
+
+
+
+ {LL.admin.node_management.help_text()}
+
+
+
+ schema={nodeInfoSchema}
+ onSubmit={handleFormSubmit}
+ defaultValues={{ userAddress: "" }}>
+ {({ register, errors, isValid }) => (
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ {searchAddress && (
+ <>
+
+
+ {error && (
+
+
+ {LL.admin.node_management.error({
+ error: error instanceof Error ? error.message : LL.admin.unknown_error(),
+ })}
+
+
+ )}
+
+ {isLoading && (
+
+
+
+ {LL.admin.node_management.loading_text()}
+
+
+ )}
+
+ {!error && userNodeInfo && (
+
+
+ {LL.admin.node_management.results_for({
+ address: formatAddress(searchAddress),
+ })}
+
+
+
+ {nodeData.map(({ label, value }) => (
+
+
+ {label}
+
+
+ {value}
+
+
+ ))}
+
+
+ )}
+
+ {!error && !userNodeInfo && !isLoading && (
+
+ {LL.admin.node_management.no_results()}
+
+ )}
+ >
+ )}
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/Contracts/components/StargateContractInfo.tsx b/apps/frontend/src/pages/Admin/components/Contracts/components/StargateContractInfo.tsx
new file mode 100644
index 00000000..1972c526
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Contracts/components/StargateContractInfo.tsx
@@ -0,0 +1,44 @@
+import { VStack, HStack, Text } from "@chakra-ui/react";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { AdminCard } from "../../common/AdminCard";
+import { useMemo } from "react";
+
+interface StargateContractInfoProps {
+ totalSupply: bigint;
+ levelIds: number[];
+}
+
+export function StargateContractInfo({ totalSupply, levelIds }: StargateContractInfoProps) {
+ const { LL } = useI18nContext();
+
+ const contractData = useMemo(
+ () => [
+ {
+ label: LL.admin.stargate_nodes.total_supply(),
+ value: totalSupply.toString(),
+ },
+ {
+ label: LL.admin.stargate_nodes.available_levels(),
+ value: `${levelIds.length} (${levelIds.join(", ")})`,
+ },
+ ],
+ [LL, totalSupply, levelIds],
+ );
+
+ return (
+
+
+ {contractData.map(({ label, value }) => (
+
+
+ {label}
+
+
+ {value}
+
+
+ ))}
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/Contracts/components/StargateLevelDetails.tsx b/apps/frontend/src/pages/Admin/components/Contracts/components/StargateLevelDetails.tsx
new file mode 100644
index 00000000..57dd7a36
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Contracts/components/StargateLevelDetails.tsx
@@ -0,0 +1,79 @@
+import { DataTable } from "@/components/ui/TableSkeleton";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { TranslationFunctions } from "@/i18n/i18n-types";
+import { Box, Heading } from "@chakra-ui/react";
+import { createColumnHelper } from "@tanstack/react-table";
+import { useMemo } from "react";
+import { veVoteService } from "../../../services/VeVoteService";
+import { BaseCell, NumberCell, TableHeader, VETAmountCell } from "../../common/AdminTableCells";
+
+interface Level {
+ name: string;
+ maturityBlocks: bigint;
+ vetAmountRequiredToStake: bigint;
+}
+
+interface StargateLevelDetailsProps {
+ levels: Level[];
+ levelIds: number[];
+}
+
+export interface StargateLevelRow {
+ levelId: number;
+ name: string;
+ maturityBlocks: bigint;
+ vetAmountRequiredToStake: bigint;
+}
+
+const columnHelper = createColumnHelper();
+
+const columns = (LL: TranslationFunctions) => [
+ columnHelper.accessor("name", {
+ cell: data => ,
+ header: () => ,
+ id: "NAME",
+ size: 160,
+ }),
+ columnHelper.accessor("maturityBlocks", {
+ cell: data => ,
+ header: () => ,
+ id: "MATURITY_BLOCKS",
+ size: 140,
+ }),
+ columnHelper.accessor("vetAmountRequiredToStake", {
+ cell: data => ,
+ header: () => ,
+ id: "VET_REQUIRED",
+ size: 140,
+ }),
+];
+
+export function StargateLevelDetails({ levels, levelIds }: StargateLevelDetailsProps) {
+ const { LL } = useI18nContext();
+
+ const tableData: StargateLevelRow[] = useMemo(() => {
+ const stargateRows = levels.map((level, index) => ({
+ levelId: levelIds[index],
+ name: level.name,
+ maturityBlocks: level.maturityBlocks,
+ vetAmountRequiredToStake: level.vetAmountRequiredToStake,
+ }));
+
+ return [veVoteService.getValidatorInfo(), ...stargateRows];
+ }, [levels, levelIds]);
+
+ if (levels.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+ {LL.admin.stargate_nodes.level_details_title()}
+
+
+
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/Contracts/components/VevoteContractInfo.tsx b/apps/frontend/src/pages/Admin/components/Contracts/components/VevoteContractInfo.tsx
new file mode 100644
index 00000000..cfa2c10a
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Contracts/components/VevoteContractInfo.tsx
@@ -0,0 +1,67 @@
+import { VStack, HStack, Text } from "@chakra-ui/react";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { useFormatTime } from "@/hooks/useFormatTime";
+import { VeVoteInfo } from "../../../services";
+import { AdminCard } from "../../common/AdminCard";
+import { FixedPointNumber, Units } from "@vechain/sdk-core";
+import { useMemo } from "react";
+
+interface VevoteContractInfoProps {
+ veVoteInfo: VeVoteInfo;
+}
+
+export function VevoteContractInfo({ veVoteInfo }: VevoteContractInfoProps) {
+ const { LL } = useI18nContext();
+ const { formatTime } = useFormatTime();
+
+ const contractData = useMemo(
+ () => [
+ {
+ label: LL.admin.vevote_contract.contract_version(),
+ value: veVoteInfo.version.toString(),
+ },
+ {
+ label: LL.admin.vevote_contract.quorum_numerator(),
+ value: `${veVoteInfo.quorumNumerator.toString()} (${((Number(veVoteInfo.quorumNumerator) / Number(veVoteInfo.quorumDenominator)) * 100).toFixed(1)}%)`,
+ },
+ {
+ label: LL.admin.vevote_contract.quorum_denominator(),
+ value: veVoteInfo.quorumDenominator.toString(),
+ },
+ {
+ label: LL.admin.vevote_contract.min_voting_delay(),
+ value: `${veVoteInfo.minVotingDelay} (${formatTime(veVoteInfo.minVotingDelay * 10)})`,
+ },
+ {
+ label: LL.admin.vevote_contract.min_voting_duration(),
+ value: `${veVoteInfo.minVotingDuration} (${formatTime(veVoteInfo.minVotingDuration * 10)})`,
+ },
+ {
+ label: LL.admin.vevote_contract.max_voting_duration(),
+ value: `${veVoteInfo.maxVotingDuration} (${formatTime(veVoteInfo.maxVotingDuration * 10)})`,
+ },
+ {
+ label: LL.admin.vevote_contract.min_staked_amount(),
+ value: LL.admin.vet_format({ amount: Units.formatEther(FixedPointNumber.of(veVoteInfo.minStakedAmount)) }),
+ },
+ ],
+ [LL.admin, formatTime, veVoteInfo],
+ );
+
+ return (
+
+
+ {contractData.map(({ label, value }) => (
+
+
+ {label}
+
+
+ {value}
+
+
+ ))}
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/Users/RoleUsersCard.tsx b/apps/frontend/src/pages/Admin/components/Users/RoleUsersCard.tsx
new file mode 100644
index 00000000..d53f7706
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Users/RoleUsersCard.tsx
@@ -0,0 +1,234 @@
+import { VStack, HStack, Text, Select, Button, FormControl, Spinner, Box, Badge, Divider } from "@chakra-ui/react";
+import { useState, useCallback, useMemo } from "react";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { AdminCard } from "../common/AdminCard";
+import { GenericInfoBox } from "@/components/ui/GenericInfoBox";
+import { CopyLink } from "@/components/ui/CopyLink";
+import { getConfig } from "@repo/config";
+import { FormSkeleton } from "@/components/ui/FormSkeleton";
+import { Label } from "@/components/ui/Label";
+import { InputMessage } from "@/components/ui/InputMessage";
+import { formatAddress } from "@/utils/address";
+import { veVoteService, type RoleUserInfo } from "../../services/VeVoteService";
+import { useThor } from "@vechain/vechain-kit";
+import { ROLES } from "./UserManagementCard";
+import { executeCall } from "@/utils/contract";
+import { VeVote__factory } from "@vechain/vevote-contracts";
+import { z } from "zod";
+
+const EXPLORER_URL = getConfig(import.meta.env.VITE_APP_ENV).network.explorerUrl;
+const contractAddress = getConfig(import.meta.env.VITE_APP_ENV).vevoteContractAddress;
+const contractInterface = VeVote__factory.createInterface();
+
+const MAX_VISIBLE_USERS = 6;
+
+const roleUsersQuerySchema = z.object({
+ selectedRole: z.enum(ROLES).or(z.literal("")),
+});
+
+type RoleUsersQuerySchema = z.infer;
+
+export function RoleUsersCard() {
+ const { LL } = useI18nContext();
+ const thor = useThor();
+ const [isLoading, setIsLoading] = useState(false);
+ const [roleUsers, setRoleUsers] = useState([]);
+ const [selectedRole, setSelectedRole] = useState<(typeof ROLES)[number] | "">("");
+ const [error, setError] = useState(null);
+
+ const defaultValues = useMemo(
+ () => ({
+ selectedRole: "" as const,
+ }),
+ [],
+ );
+
+ const onSubmit = useCallback(
+ async (values: RoleUsersQuerySchema) => {
+ if (!thor) {
+ setError("Please connect your wallet");
+ return;
+ }
+
+ setIsLoading(true);
+ setError(null);
+ setSelectedRole(values.selectedRole);
+
+ try {
+ const roleRes = await executeCall({
+ contractAddress,
+ contractInterface,
+ method: values.selectedRole as (typeof ROLES)[number],
+ args: [],
+ });
+
+ if (!roleRes.success) {
+ throw new Error(LL.admin.unknown_error());
+ }
+
+ const roleHash = roleRes.result.plain as string;
+ const users = await veVoteService.getRoleUsers(thor, roleHash);
+ setRoleUsers(users);
+ } catch (err) {
+ const error = err as Error;
+ setError(error.message);
+ setRoleUsers([]);
+ } finally {
+ setIsLoading(false);
+ }
+ },
+ [thor, LL],
+ );
+
+ const hasQueried = useMemo(() => Boolean(selectedRole), [selectedRole]);
+ const showScrollableList = useMemo(() => roleUsers.length > MAX_VISIBLE_USERS, [roleUsers.length]);
+
+ return (
+
+
+
+ {LL.admin.role_users.help_text()}
+
+
+
+ {({ register, errors, watch }) => {
+ const isButtonDisabled = watch("selectedRole") === "";
+ return (
+
+
+
+
+
+
+
+
+
+ );
+ }}
+
+
+ {hasQueried && (
+ <>
+
+
+ {error && (
+
+ {LL.admin.role_users.error_description({ error })}
+
+ )}
+
+ {isLoading && (
+
+
+
+ {LL.admin.role_users.loading_text()}
+
+
+ )}
+
+ {!error && !isLoading && roleUsers.length === 0 && (
+
+ {LL.admin.role_users.no_users()}
+
+ )}
+
+ {!error && !isLoading && roleUsers.length > 0 && (
+
+
+
+ {LL.admin.role_users.results_title()}
+
+
+ {LL.admin.role_users.user_count({ count: roleUsers.length })}
+
+
+
+
+ {LL.admin.role_users.role_selected({
+ role: LL.admin.common_roles[selectedRole as (typeof ROLES)[number]](),
+ })}
+
+
+
+
+ {showScrollableList && (
+
+ {LL.admin.role_users.scrollable_hint()}
+
+ )}
+
+ )}
+ >
+ )}
+
+
+ );
+}
+
+const UserWithRoleList = ({
+ roleUsers,
+ showScrollableList,
+}: {
+ roleUsers: RoleUserInfo[];
+ showScrollableList: boolean;
+}) => {
+ const { LL } = useI18nContext();
+ return (
+
+
+ {roleUsers.map((user, index) => (
+
+
+
+ {formatAddress(user.address)}
+
+
+ {LL.admin.role_users.granted_at({ date: user.grantedAt.toLocaleDateString() })}
+
+
+
+ {LL.admin.role_users.view_tx()}
+
+
+ ))}
+
+
+ );
+};
diff --git a/apps/frontend/src/pages/Admin/components/Users/UserManagementCard.tsx b/apps/frontend/src/pages/Admin/components/Users/UserManagementCard.tsx
new file mode 100644
index 00000000..cb7bb7df
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Users/UserManagementCard.tsx
@@ -0,0 +1,250 @@
+import { FormSkeleton, FormSkeletonProps } from "@/components/ui/FormSkeleton";
+import { InputMessage } from "@/components/ui/InputMessage";
+import { Label } from "@/components/ui/Label";
+import { MessageModal } from "@/components/ui/ModalSkeleton";
+import { useRoleManagement } from "@/hooks/useRoleManagement";
+import { useUserAdminRoles } from "@/hooks/useAdminUserRoles";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { CheckIcon } from "@/icons";
+import { userManagementSchema, type UserManagementSchema } from "@/schema/adminSchema";
+import { executeCall } from "@/utils/contract";
+import { isValidAddress } from "@/utils/zod";
+import {
+ Badge,
+ Box,
+ Button,
+ Divider,
+ FormControl,
+ HStack,
+ Input,
+ Select,
+ Spinner,
+ Text,
+ useDisclosure,
+ VStack,
+ Wrap,
+ WrapItem,
+} from "@chakra-ui/react";
+import { getConfig } from "@repo/config";
+import { VeVote__factory } from "@vechain/vevote-contracts";
+import { useCallback, useMemo } from "react";
+import { AdminCard } from "../common/AdminCard";
+import { SensitiveWarning } from "../common/SensitiveWarning";
+
+export const ROLES = [
+ "DEFAULT_ADMIN_ROLE",
+ "EXECUTOR_ROLE",
+ "SETTINGS_MANAGER_ROLE",
+ "NODE_WEIGHT_MANAGER_ROLE",
+ "UPGRADER_ROLE",
+ "WHITELISTED_ROLE",
+ "WHITELIST_ADMIN_ROLE",
+] as const;
+
+const contractAddress = getConfig(import.meta.env.VITE_APP_ENV).vevoteContractAddress;
+const contractInterface = VeVote__factory.createInterface();
+
+export function UserManagementCard() {
+ const { LL } = useI18nContext();
+ const { sendTransaction, isTransactionPending } = useRoleManagement();
+
+ const { isOpen: isSuccessOpen, onClose: onSuccessClose, onOpen: onSuccessOpen } = useDisclosure();
+
+ const defaultValues = useMemo(
+ () => ({
+ userAddress: "",
+ selectedRole: "",
+ }),
+ [],
+ );
+
+ const handleRoleAction = useCallback(
+ async (action: "grant" | "revoke", values: UserManagementSchema) => {
+ const nodeRes = await executeCall({
+ contractAddress,
+ contractInterface,
+ method: values.selectedRole as (typeof ROLES)[number],
+ args: [],
+ });
+
+ if (!nodeRes.success) {
+ throw new Error(LL.admin.common_roles.error_description({ error: LL.admin.unknown_error() }));
+ }
+
+ const role = nodeRes.result.plain as (typeof ROLES)[number];
+
+ await sendTransaction({
+ action,
+ role,
+ account: values.userAddress,
+ });
+
+ onSuccessOpen();
+ },
+ [LL.admin, sendTransaction, onSuccessOpen],
+ );
+
+ const handleFormSubmit: FormSkeletonProps["onSubmit"] = useCallback(
+ async (values, _, action) => {
+ try {
+ const roleAction = action === "revoke" ? "revoke" : "grant";
+ await handleRoleAction(roleAction, values);
+ } catch (err) {
+ const error = err as Error;
+ console.error(error);
+ }
+ },
+ [handleRoleAction],
+ );
+
+ return (
+ <>
+
+
+ {LL.admin.user_management.description()}
+
+
+
+ schema={userManagementSchema}
+ onSubmit={handleFormSubmit}
+ defaultValues={defaultValues}>
+ {({ register, errors, watch, isValid }) => {
+ const watchedAddress = watch("userAddress");
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }}
+
+
+
+
+
+
+
+ {LL.admin.common_roles.grant_success_description()}
+
+
+ >
+ );
+}
+
+const UserRolesSection = ({ userAddress }: { userAddress: string }) => {
+ const { LL } = useI18nContext();
+ const { data: userRoles, isLoading: isLoadingRoles } = useUserAdminRoles(
+ userAddress && isValidAddress(userAddress) ? userAddress : "",
+ );
+
+ const isUserRoles = useMemo(() => Boolean(userRoles && userRoles.length > 0), [userRoles]);
+
+ if (!userAddress || !isValidAddress(userAddress)) {
+ return null;
+ }
+
+ return (
+
+
+ {LL.admin.user_management.current_roles_label()}
+
+
+
+ );
+};
+
+const RoleSection = ({
+ isLoadingRoles,
+ isUserRoles,
+ userRoles,
+}: {
+ isLoadingRoles: boolean;
+ isUserRoles: boolean;
+ userRoles?: (typeof ROLES)[number][];
+}) => {
+ const { LL } = useI18nContext();
+ if (isLoadingRoles) {
+ return (
+
+
+
+ {LL.admin.user_management.checking_roles()}
+
+
+ );
+ }
+
+ if (isUserRoles) {
+ return (
+
+ {userRoles?.map(role => (
+
+
+ {LL.admin.common_roles[role]()}
+
+
+ ))}
+
+ );
+ }
+
+ return (
+
+ {LL.admin.user_management.no_roles_assigned()}
+
+ );
+};
diff --git a/apps/frontend/src/pages/Admin/components/Users/VotingPowerAtTimepointCard.tsx b/apps/frontend/src/pages/Admin/components/Users/VotingPowerAtTimepointCard.tsx
new file mode 100644
index 00000000..3c928d55
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Users/VotingPowerAtTimepointCard.tsx
@@ -0,0 +1,213 @@
+import { Box, Button, FormControl, Input, Text, VStack, HStack, Divider } from "@chakra-ui/react";
+import { useState, useCallback, useMemo } from "react";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { AdminCard } from "../common/AdminCard";
+import { GenericInfoBox } from "@/components/ui/GenericInfoBox";
+import { useVotingPowerAtTimepoint, VotingPowerAtTimepointResult } from "../../hooks/useVotingPowerAtTimepoint";
+import { useVechainDomainOrAddress } from "@/hooks/useVechainDomainOrAddress";
+import { CopyLink } from "@/components/ui/CopyLink";
+import { getConfig } from "@repo/config";
+import { FormSkeleton } from "@/components/ui/FormSkeleton";
+import { Label } from "@/components/ui/Label";
+import { InputMessage } from "@/components/ui/InputMessage";
+import { votingPowerQuerySchema, VotingPowerQuerySchema } from "@/schema/adminSchema";
+import { formatAddress } from "@/utils/address";
+import { formatVotingPower } from "@/utils/proposals/helpers";
+
+const EXPLORER_URL = getConfig(import.meta.env.VITE_APP_ENV).network.explorerUrl;
+
+export function VotingPowerAtTimepointCard() {
+ const { LL } = useI18nContext();
+
+ const [queryParams, setQueryParams] = useState<{
+ address?: string;
+ timepoint?: number;
+ masterAddress?: string;
+ }>({ address: undefined, timepoint: undefined, masterAddress: undefined });
+
+ const defaultValues = useMemo(
+ () => ({
+ address: "",
+ timepoint: undefined,
+ masterAddress: "",
+ }),
+ [],
+ );
+
+ const onSubmit = useCallback(async (values: VotingPowerQuerySchema) => {
+ setQueryParams({
+ address: values.address,
+ timepoint: values.timepoint,
+ masterAddress: values.masterAddress || undefined,
+ });
+ }, []);
+
+ const { data: votingPowerData, isLoading, error } = useVotingPowerAtTimepoint(queryParams);
+
+ return (
+
+
+
+ {LL.admin.voting_power_timepoint.help_text()}
+
+
+
+ {({ register, errors }) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ );
+}
+
+const QueryResults = ({
+ queryParams,
+ error,
+ votingPowerData,
+ isLoading,
+}: {
+ queryParams: { address?: string; timepoint?: number; masterAddress?: string };
+ error: Error | null;
+ votingPowerData?: VotingPowerAtTimepointResult;
+ isLoading: boolean;
+}) => {
+ const { LL } = useI18nContext();
+
+ const { addressOrDomain } = useVechainDomainOrAddress(queryParams.address);
+
+ const hasQueried = useMemo(() => Boolean(queryParams.address && queryParams.timepoint !== undefined), [queryParams]);
+
+ if (!hasQueried) return null;
+ return (
+ <>
+
+
+ {error && (
+
+
+ {LL.admin.voting_power_timepoint.error_description({
+ error: error instanceof Error ? error.message : LL.admin.unknown_error(),
+ })}
+
+
+ )}
+
+ {!error && votingPowerData && (
+
+
+ {LL.admin.voting_power_timepoint.results_title()}
+
+
+
+
+ {LL.admin.voting_power_timepoint.address()}
+
+ {addressOrDomain}
+
+
+
+
+ {LL.admin.voting_power_timepoint.timepoint()}
+ {queryParams.timepoint?.toString()}
+
+
+ {queryParams.masterAddress && (
+
+ {LL.admin.voting_power_timepoint.master_address()}
+
+ {formatAddress(queryParams.masterAddress)}
+
+
+ )}
+
+
+
+
+
+ {LL.admin.voting_power_timepoint.node_power_label()}
+
+ {formatVotingPower(votingPowerData.nodePower)}
+
+
+
+
+ {LL.admin.voting_power_timepoint.validator_power_label()}
+
+ {formatVotingPower(votingPowerData.validatorPower)}
+
+
+
+
+
+
+ {LL.admin.voting_power_timepoint.total_power_label()}
+
+
+ {formatVotingPower(votingPowerData.totalPower)}
+
+
+
+
+ )}
+
+ {!error && !votingPowerData && !isLoading && (
+
+ {LL.admin.voting_power_timepoint.no_results()}
+
+ )}
+ >
+ );
+};
diff --git a/apps/frontend/src/pages/Admin/components/Utils/GovernanceSettings.tsx b/apps/frontend/src/pages/Admin/components/Utils/GovernanceSettings.tsx
new file mode 100644
index 00000000..f1915266
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Utils/GovernanceSettings.tsx
@@ -0,0 +1,42 @@
+import { Flex, Text, useDisclosure } from "@chakra-ui/react";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { useVeVoteInfo } from "../../hooks";
+import { MessageModal } from "@/components/ui/ModalSkeleton";
+import { CheckIcon } from "@/icons";
+import { GovernanceSettingsForm } from "./GovernanceSettingsForm";
+import { LevelMultipliersCard } from "./LevelMultipliersCard";
+import { GenericInfoBox } from "@/components/ui/GenericInfoBox";
+
+export function GovernanceSettings() {
+ const { LL } = useI18nContext();
+ const { data: veVoteInfo, isLoading, error } = useVeVoteInfo();
+
+ const { isOpen: isSuccessOpen, onClose: onSuccessClose, onOpen: onSuccessOpen } = useDisclosure();
+
+ if (isLoading) return {LL.admin.vevote_contract.loading()};
+
+ if (error || !veVoteInfo) {
+ return (
+
+ {LL.admin.vevote_contract.no_data()}
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ {LL.admin.governance_settings.success_description()}
+
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/Utils/GovernanceSettingsForm.tsx b/apps/frontend/src/pages/Admin/components/Utils/GovernanceSettingsForm.tsx
new file mode 100644
index 00000000..b2d9853f
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Utils/GovernanceSettingsForm.tsx
@@ -0,0 +1,179 @@
+import { FormSkeleton } from "@/components/ui/FormSkeleton";
+import { InputMessage } from "@/components/ui/InputMessage";
+import { Label } from "@/components/ui/Label";
+import { useFormatTime } from "@/hooks/useFormatTime";
+import { useGovernanceSettings } from "@/hooks/useGovernanceSettings";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { governanceSettingsSchema, GovernanceSettingsSchema } from "@/schema/adminSchema";
+import { Button, Flex, FormControl, NumberInput, NumberInputField, SimpleGrid, Text } from "@chakra-ui/react";
+import { FixedPointNumber, Units } from "@vechain/sdk-core";
+import { useCallback, useMemo } from "react";
+import { VeVoteInfo } from "../../services";
+import { AdminCard } from "../common/AdminCard";
+import { SensitiveWarning } from "../common/SensitiveWarning";
+
+interface GovernanceSettingsFormProps {
+ veVoteInfo: VeVoteInfo;
+ onSuccess?: () => void;
+}
+
+export function GovernanceSettingsForm({ veVoteInfo, onSuccess }: GovernanceSettingsFormProps) {
+ const { LL } = useI18nContext();
+ const { sendTransaction, isTransactionPending } = useGovernanceSettings();
+ const { formatTime } = useFormatTime();
+
+ const defaultValues = useMemo(
+ () => ({
+ quorumNumerator: undefined,
+ minVotingDelay: undefined,
+ minVotingDuration: undefined,
+ maxVotingDuration: undefined,
+ minStakedVetAmount: undefined,
+ }),
+ [],
+ );
+
+ const onSubmit = useCallback(
+ async (values: GovernanceSettingsSchema) => {
+ const { quorumNumerator, minVotingDelay, minVotingDuration, maxVotingDuration, minStakedVetAmount } = values;
+ const updateParams: {
+ updateQuorumNumerator?: number;
+ setMinVotingDelay?: number;
+ setMinVotingDuration?: number;
+ setMaxVotingDuration?: number;
+ setMinStakedVetAmount?: bigint;
+ } = {};
+
+ if (quorumNumerator && quorumNumerator !== Number(veVoteInfo.quorumNumerator)) {
+ updateParams.updateQuorumNumerator = quorumNumerator;
+ }
+
+ if (minVotingDelay && minVotingDelay !== veVoteInfo.minVotingDelay) {
+ updateParams.setMinVotingDelay = minVotingDelay;
+ }
+
+ if (minVotingDuration && minVotingDuration !== veVoteInfo.minVotingDuration) {
+ updateParams.setMinVotingDuration = minVotingDuration;
+ }
+
+ if (maxVotingDuration && maxVotingDuration !== veVoteInfo.maxVotingDuration) {
+ updateParams.setMaxVotingDuration = maxVotingDuration;
+ }
+
+ if (minStakedVetAmount) {
+ // Convert VET to wei using parseEther
+ const newValue = BigInt(Units.parseEther(minStakedVetAmount.toString()).toString());
+ if (newValue !== veVoteInfo.minStakedAmount) {
+ updateParams.setMinStakedVetAmount = newValue;
+ }
+ }
+
+ if (Object.keys(updateParams).length === 0) return;
+
+ await sendTransaction(updateParams);
+ onSuccess?.();
+ },
+ [veVoteInfo, sendTransaction, onSuccess],
+ );
+
+ return (
+
+
+ {LL.admin.governance_settings.description()}
+
+
+
+ {({ register, errors, isDirty }) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }}
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/Utils/LevelMultipliersCard.tsx b/apps/frontend/src/pages/Admin/components/Utils/LevelMultipliersCard.tsx
new file mode 100644
index 00000000..41b1f8a4
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Utils/LevelMultipliersCard.tsx
@@ -0,0 +1,205 @@
+import { FormSkeleton } from "@/components/ui/FormSkeleton";
+import { GenericInfoBox } from "@/components/ui/GenericInfoBox";
+import { DataTable } from "@/components/ui/TableSkeleton";
+import { useLevelMultipliers } from "@/hooks/useLevelMultipliers";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { Button, Flex, Text } from "@chakra-ui/react";
+import { createColumnHelper } from "@tanstack/react-table";
+import { useCallback, useMemo } from "react";
+import { z } from "zod";
+import { useLevelMultipliersInfo } from "../../hooks/useLevelMultipliersInfo";
+import { AdminCard } from "../common/AdminCard";
+import { BaseCell, MultiplierInputCell, TableHeader } from "../common/AdminTableCells";
+import { SensitiveWarning } from "../common/SensitiveWarning";
+
+interface LevelMultipliersCardProps {
+ onSuccess?: () => void;
+}
+
+const getDefaultMultiplierForLevel = (levelId: number): number => {
+ const defaults = [200, 100, 100, 100, 150, 150, 150, 150, 100, 100, 100];
+ return defaults[levelId] || 100;
+};
+
+const multiplierSchema = z.object({
+ multiplier0: z.coerce.number().min(0).max(1000).optional(),
+ multiplier1: z.coerce.number().min(0).max(1000).optional(),
+ multiplier2: z.coerce.number().min(0).max(1000).optional(),
+ multiplier3: z.coerce.number().min(0).max(1000).optional(),
+ multiplier4: z.coerce.number().min(0).max(1000).optional(),
+ multiplier5: z.coerce.number().min(0).max(1000).optional(),
+ multiplier6: z.coerce.number().min(0).max(1000).optional(),
+ multiplier7: z.coerce.number().min(0).max(1000).optional(),
+ multiplier8: z.coerce.number().min(0).max(1000).optional(),
+ multiplier9: z.coerce.number().min(0).max(1000).optional(),
+ multiplier10: z.coerce.number().min(0).max(1000).optional(),
+});
+
+type MultiplierSchema = z.infer;
+
+interface LevelMultiplierRow {
+ id: number;
+ name: string;
+ currentMultiplier: number;
+}
+
+const columnHelper = createColumnHelper();
+
+export function LevelMultipliersCard({ onSuccess }: LevelMultipliersCardProps) {
+ const { LL } = useI18nContext();
+ const { sendTransaction, isTransactionPending } = useLevelMultipliers();
+ const {
+ data: currentMultipliers,
+ isLoading: isLoadingMultipliers,
+ error: multipliersError,
+ } = useLevelMultipliersInfo();
+
+ const defaultValues = useMemo(
+ () => ({
+ multiplier0: undefined,
+ multiplier1: undefined,
+ multiplier2: undefined,
+ multiplier3: undefined,
+ multiplier4: undefined,
+ multiplier5: undefined,
+ multiplier6: undefined,
+ multiplier7: undefined,
+ multiplier8: undefined,
+ multiplier9: undefined,
+ multiplier10: undefined,
+ }),
+ [],
+ );
+
+ const onSubmit = useCallback(
+ async (values: MultiplierSchema) => {
+ const multipliers = Array.from({ length: 11 }, (_, index) => {
+ const fieldKey = `multiplier${index}` as keyof MultiplierSchema;
+ const formValue = values[fieldKey];
+
+ return formValue !== undefined
+ ? formValue
+ : (currentMultipliers?.[index] ?? getDefaultMultiplierForLevel(index));
+ });
+
+ await sendTransaction({
+ updateLevelIdMultipliers: multipliers,
+ });
+
+ onSuccess?.();
+ },
+ [currentMultipliers, sendTransaction, onSuccess],
+ );
+
+ const levelData = useMemo(
+ () => [
+ { id: 0, name: LL.admin.governance_settings.validator_multiplier() },
+ { id: 1, name: LL.admin.governance_settings.strength() },
+ { id: 2, name: LL.admin.governance_settings.thunder() },
+ { id: 3, name: LL.admin.governance_settings.mjolnir() },
+ { id: 4, name: LL.admin.governance_settings.vethor_x() },
+ { id: 5, name: LL.admin.governance_settings.strength_x() },
+ { id: 6, name: LL.admin.governance_settings.thunder_x() },
+ { id: 7, name: LL.admin.governance_settings.mjolnir_x() },
+ { id: 8, name: LL.admin.governance_settings.dawn() },
+ { id: 9, name: LL.admin.governance_settings.lightning() },
+ { id: 10, name: LL.admin.governance_settings.flash() },
+ ],
+ [LL.admin.governance_settings],
+ );
+
+ const getCurrentMultiplier = useCallback(
+ (index: number) => {
+ return currentMultipliers?.[index] ?? getDefaultMultiplierForLevel(index);
+ },
+ [currentMultipliers],
+ );
+
+ const tableData: LevelMultiplierRow[] = useMemo(
+ () =>
+ levelData.map(level => ({
+ ...level,
+ currentMultiplier: getCurrentMultiplier(level.id),
+ })),
+ [levelData, getCurrentMultiplier],
+ );
+
+ const columns = useMemo(
+ () => [
+ columnHelper.accessor("name", {
+ cell: data => ,
+ header: () => ,
+ id: "NODE_NAME",
+ size: 200,
+ }),
+ columnHelper.accessor("currentMultiplier", {
+ cell: data => ,
+ header: () => ,
+ id: "CURRENT_MULTIPLIER",
+ size: 140,
+ }),
+ columnHelper.accessor("id", {
+ cell: data => (
+
+ ),
+ header: () => ,
+ id: "NEW_MULTIPLIER",
+ size: 120,
+ }),
+ ],
+ [LL.admin.governance_settings],
+ );
+
+ if (isLoadingMultipliers) {
+ return (
+
+ {LL.admin.vevote_contract.loading()}
+
+ );
+ }
+
+ if (multipliersError) {
+ return (
+
+
+ Error loading multipliers
+
+
+ );
+ }
+
+ return (
+
+
+ {LL.admin.governance_settings.level_multipliers_help()}
+
+
+
+ {({ watch }) => {
+ const watchedValues = watch();
+ const hasChanges = Object.values(watchedValues).some(
+ value => value !== undefined && value !== null && value !== "",
+ );
+
+ return (
+
+
+
+
+ );
+ }}
+
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/Utils/UserManagement.tsx b/apps/frontend/src/pages/Admin/components/Utils/UserManagement.tsx
new file mode 100644
index 00000000..ccbe2355
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/Utils/UserManagement.tsx
@@ -0,0 +1,25 @@
+import { Flex, useBreakpointValue } from "@chakra-ui/react";
+import { RoleUsersCard } from "../Users/RoleUsersCard";
+import { UserManagementCard } from "../Users/UserManagementCard";
+import { VotingPowerAtTimepointCard } from "../Users/VotingPowerAtTimepointCard";
+
+export function UserManagement() {
+ const stackSpacing = useBreakpointValue({
+ base: 4,
+ md: 6,
+ });
+
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/common/AdminCard.tsx b/apps/frontend/src/pages/Admin/components/common/AdminCard.tsx
new file mode 100644
index 00000000..b825fc43
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/common/AdminCard.tsx
@@ -0,0 +1,42 @@
+import { Box, Heading, BoxProps, useBreakpointValue } from "@chakra-ui/react";
+import { ReactNode } from "react";
+
+interface AdminCardProps extends BoxProps {
+ title: string;
+ children: ReactNode;
+}
+
+export function AdminCard({ title, children, ...boxProps }: AdminCardProps) {
+ const cardWidth = useBreakpointValue({
+ base: "100%",
+ md: "fit-content",
+ });
+
+ const width = useBreakpointValue({
+ base: "auto",
+ md: 400,
+ });
+
+ const padding = useBreakpointValue({
+ base: 3,
+ md: 4,
+ });
+
+ return (
+
+
+ {title}
+
+ {children}
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/common/AdminTableCells.tsx b/apps/frontend/src/pages/Admin/components/common/AdminTableCells.tsx
new file mode 100644
index 00000000..fd098070
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/common/AdminTableCells.tsx
@@ -0,0 +1,68 @@
+import { Text, TextProps, FormControl, Input } from "@chakra-ui/react";
+import { FixedPointNumber, Units } from "@vechain/sdk-core";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { useFormContext } from "react-hook-form";
+
+export const TableHeader = ({ label }: { label: string }) => {
+ return (
+
+ {label}
+
+ );
+};
+
+export const BaseCell = ({ value, ...restProps }: TextProps & { value: string | number }) => {
+ return (
+
+ {value}
+
+ );
+};
+
+export const VETAmountCell = ({ amount }: { amount: bigint }) => {
+ const { LL } = useI18nContext();
+
+ return (
+
+ );
+};
+
+export const NumberCell = ({ value, suffix }: { value: number | bigint; suffix?: string }) => {
+ const displayValue = typeof value === "bigint" ? value.toString() : value.toLocaleString();
+ return ;
+};
+
+export const MultiplierInputCell = ({ fieldName, placeholder }: { fieldName: string; placeholder: string }) => {
+ const {
+ register,
+ formState: { errors },
+ } = useFormContext();
+
+ return (
+
+
+
+ );
+};
diff --git a/apps/frontend/src/pages/Admin/components/common/SensitiveWarning.tsx b/apps/frontend/src/pages/Admin/components/common/SensitiveWarning.tsx
new file mode 100644
index 00000000..cbf4b40f
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/common/SensitiveWarning.tsx
@@ -0,0 +1,14 @@
+import { GenericInfoBox } from "@/components/ui/GenericInfoBox";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { Text } from "@chakra-ui/react";
+
+export const SensitiveWarning = () => {
+ const { LL } = useI18nContext();
+ return (
+
+
+ {LL.admin.sensitive_operation_warning()}
+
+
+ );
+};
diff --git a/apps/frontend/src/pages/Admin/components/common/UserRoleChecker.tsx b/apps/frontend/src/pages/Admin/components/common/UserRoleChecker.tsx
new file mode 100644
index 00000000..a40c6133
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/common/UserRoleChecker.tsx
@@ -0,0 +1,59 @@
+import { useI18nContext } from "@/i18n/i18n-react";
+import { CheckIcon, CircleXIcon } from "@/icons";
+import { HStack, Icon, Spinner, Text, VStack } from "@chakra-ui/react";
+import { useContractRoles } from "@/hooks/useContractRoles";
+import { useWallet } from "@vechain/vechain-kit";
+import { ContractType } from "@/pages/Admin/constants/contracts";
+import { AdminCard } from "./AdminCard";
+
+interface UserRoleCheckerProps {
+ contractType?: ContractType;
+}
+
+export function UserRoleChecker({ contractType }: UserRoleCheckerProps) {
+ const { LL } = useI18nContext();
+ const { account } = useWallet();
+ const { defaultRoles, roles, isLoading } = useContractRoles(contractType);
+
+ if (!account?.address) {
+ return (
+
+
+ {LL.admin.user_role_checker.connect_wallet_message()}
+
+
+ );
+ }
+
+ if (isLoading) {
+ return (
+
+
+
+
+ {LL.admin.user_role_checker.checking_roles()}
+
+
+
+ );
+ }
+
+ return (
+
+
+ {defaultRoles.map(role => {
+ const hasRole = roles?.includes(role) || false;
+
+ return (
+
+
+ {LL.admin.common_roles[role]()}
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/navigation/MobileNavDrawer.tsx b/apps/frontend/src/pages/Admin/components/navigation/MobileNavDrawer.tsx
new file mode 100644
index 00000000..ca098440
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/navigation/MobileNavDrawer.tsx
@@ -0,0 +1,133 @@
+import {
+ Drawer,
+ DrawerBody,
+ DrawerCloseButton,
+ DrawerContent,
+ DrawerHeader,
+ DrawerOverlay,
+ VStack,
+ Text,
+ Box,
+ Accordion,
+ AccordionItem,
+ AccordionButton,
+ AccordionPanel,
+ Button,
+ useColorModeValue,
+} from "@chakra-ui/react";
+import { ChevronDownIcon } from "@/icons";
+import { useI18nContext } from "@/i18n/i18n-react";
+
+interface MobileNavDrawerProps {
+ isOpen: boolean;
+ onClose: () => void;
+ activeMainTab: number;
+ activeSubTab: number;
+ onMainTabChange: (index: number) => void;
+ onSubTabChange: (index: number) => void;
+}
+
+export function MobileNavDrawer({
+ isOpen,
+ onClose,
+ activeMainTab,
+ activeSubTab,
+ onMainTabChange,
+ onSubTabChange,
+}: MobileNavDrawerProps) {
+ const { LL } = useI18nContext();
+
+ const bgColor = useColorModeValue("white", "gray.800");
+ const borderColor = useColorModeValue("gray.200", "gray.600");
+
+ const contractsSubTabs = [
+ { label: LL.admin.contracts.vevote(), index: 0, id: "vevote" },
+ { label: LL.admin.contracts.node_management(), index: 1, id: "node-management" },
+ { label: LL.admin.contracts.stargate_nodes(), index: 2, id: "stargate-nodes" },
+ ];
+
+ const utilsSubTabs = [
+ { label: LL.admin.tabs.users(), index: 0, id: "users" },
+ { label: LL.admin.tabs.governance_settings(), index: 1, id: "governance-settings" },
+ ];
+
+ const handleNavigation = (mainTabIndex: number, subTabIndex: number) => {
+ onMainTabChange(mainTabIndex);
+ onSubTabChange(subTabIndex);
+ onClose();
+ };
+
+ return (
+
+
+
+
+
+ {LL.admin.title()}
+
+
+
+
+ {/* Contracts Section */}
+
+
+
+ {LL.admin.tabs.contracts()}
+
+
+
+
+
+ {contractsSubTabs.map(tab => (
+
+ ))}
+
+
+
+
+ {/* Utils Section */}
+
+
+
+ {LL.admin.tabs.utils()}
+
+
+
+
+
+ {utilsSubTabs.map(tab => (
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/navigation/NavigationComponents.tsx b/apps/frontend/src/pages/Admin/components/navigation/NavigationComponents.tsx
new file mode 100644
index 00000000..ae71a78c
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/navigation/NavigationComponents.tsx
@@ -0,0 +1,86 @@
+import { Box, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
+import { useI18nContext } from "@/i18n/i18n-react";
+import { useMemo } from "react";
+
+const contractKeys = ["vevote-contract", "node-management", "stargate-nodes"];
+const utilsKeys = ["user-management", "governance-settings"];
+
+interface MainTabsProps {
+ mainTabIndex: number;
+ onMainTabChange: (index: number) => void;
+ children: React.ReactNode;
+}
+
+export function MainTabs({ mainTabIndex, onMainTabChange, children }: MainTabsProps) {
+ const { LL } = useI18nContext();
+
+ return (
+
+
+ {LL.admin.tabs.contracts()}
+ {LL.admin.tabs.utils()}
+
+ {children}
+
+ );
+}
+
+interface HorizontalTabsProps {
+ tabIndex: number;
+ onTabChange: (index: number) => void;
+ content: React.ReactNode[];
+ type: "contracts" | "utils";
+}
+
+export function HorizontalTabs({ tabIndex, onTabChange, content, type }: HorizontalTabsProps) {
+ const { LL } = useI18nContext();
+ const keys = useMemo(() => (type === "contracts" ? contractKeys : utilsKeys), [type]);
+ const tabLabels = useMemo(
+ () =>
+ type === "contracts"
+ ? [LL.admin.contracts.vevote(), LL.admin.contracts.node_management(), LL.admin.contracts.stargate_nodes()]
+ : [LL.admin.tabs.users(), LL.admin.tabs.governance_settings()],
+ [LL, type],
+ );
+
+ return (
+
+
+ {tabLabels.map(label => (
+ {label}
+ ))}
+
+
+
+ {content.map((item, index) => (
+
+ {item}
+
+ ))}
+
+
+ );
+}
+
+interface MobileContentProps {
+ mainTabIndex: number;
+ contractsTabIndex: number;
+ utilsTabIndex: number;
+ contractsContent: React.ReactNode[];
+ utilsContent: React.ReactNode[];
+}
+
+export function MobileContent({
+ mainTabIndex,
+ contractsTabIndex,
+ utilsTabIndex,
+ contractsContent,
+ utilsContent,
+}: MobileContentProps) {
+ const currentContent = useMemo(
+ () => (mainTabIndex === 0 ? contractsContent[contractsTabIndex] : utilsContent[utilsTabIndex]),
+ [mainTabIndex, contractsTabIndex, utilsTabIndex, contractsContent, utilsContent],
+ );
+
+ return {currentContent};
+}
diff --git a/apps/frontend/src/pages/Admin/components/navigation/ResponsiveHeader.tsx b/apps/frontend/src/pages/Admin/components/navigation/ResponsiveHeader.tsx
new file mode 100644
index 00000000..4fd494dc
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/navigation/ResponsiveHeader.tsx
@@ -0,0 +1,35 @@
+import { Box, Heading, HStack, IconButton } from "@chakra-ui/react";
+import { MenuIcon } from "@/icons";
+import { ConnectButton } from "@/components/ui/ConnectButton";
+import { useI18nContext } from "@/i18n/i18n-react";
+
+interface ResponsiveHeaderProps {
+ showMenuButton: boolean;
+ onMenuToggle: () => void;
+}
+
+export function ResponsiveHeader({ showMenuButton, onMenuToggle }: ResponsiveHeaderProps) {
+ const { LL } = useI18nContext();
+
+ return (
+
+
+ {showMenuButton && (
+ }
+ variant="ghost"
+ size="md"
+ onClick={onMenuToggle}
+ />
+ )}
+
+ {LL.admin.title()}
+
+
+
+
+
+
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/navigation/ResponsiveNavigation.tsx b/apps/frontend/src/pages/Admin/components/navigation/ResponsiveNavigation.tsx
new file mode 100644
index 00000000..eb66285e
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/navigation/ResponsiveNavigation.tsx
@@ -0,0 +1,47 @@
+import { useMemo, useState } from "react";
+import { useResponsiveNavigation } from "../../hooks/useResponsiveNavigation";
+import { HorizontalLayout } from "./layouts/HorizontalLayout";
+import { MobileLayout } from "./layouts/MobileLayout";
+
+interface ResponsiveNavigationProps {
+ contractsContent: React.ReactNode[];
+ utilsContent: React.ReactNode[];
+}
+
+export function ResponsiveNavigation({ contractsContent, utilsContent }: ResponsiveNavigationProps) {
+ const { showMobileDrawer, drawerState } = useResponsiveNavigation();
+
+ const [mainTabIndex, setMainTabIndex] = useState(0);
+ const [contractsTabIndex, setContractsTabIndex] = useState(0);
+ const [utilsTabIndex, setUtilsTabIndex] = useState(0);
+
+ const handleMainTabChange = (index: number) => {
+ setMainTabIndex(index);
+ };
+
+ const handleContractsTabChange = (index: number) => {
+ setContractsTabIndex(index);
+ };
+
+ const handleUtilsTabChange = (index: number) => {
+ setUtilsTabIndex(index);
+ };
+
+ const commonProps = useMemo(
+ () => ({
+ mainTabIndex,
+ contractsTabIndex,
+ utilsTabIndex,
+ onMainTabChange: handleMainTabChange,
+ onContractsTabChange: handleContractsTabChange,
+ onUtilsTabChange: handleUtilsTabChange,
+ contractsContent,
+ utilsContent,
+ }),
+ [mainTabIndex, contractsTabIndex, utilsTabIndex, contractsContent, utilsContent],
+ );
+
+ if (showMobileDrawer) return ;
+
+ return ;
+}
diff --git a/apps/frontend/src/pages/Admin/components/navigation/layouts/HorizontalLayout.tsx b/apps/frontend/src/pages/Admin/components/navigation/layouts/HorizontalLayout.tsx
new file mode 100644
index 00000000..1c106039
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/navigation/layouts/HorizontalLayout.tsx
@@ -0,0 +1,45 @@
+import { TabPanel } from "@chakra-ui/react";
+import { MainTabs, HorizontalTabs } from "../NavigationComponents";
+import { ResponsiveHeader } from "../ResponsiveHeader";
+
+interface HorizontalLayoutProps {
+ mainTabIndex: number;
+ contractsTabIndex: number;
+ utilsTabIndex: number;
+ onMainTabChange: (index: number) => void;
+ onContractsTabChange: (index: number) => void;
+ onUtilsTabChange: (index: number) => void;
+ contractsContent: React.ReactNode[];
+ utilsContent: React.ReactNode[];
+}
+
+export function HorizontalLayout({
+ mainTabIndex,
+ contractsTabIndex,
+ utilsTabIndex,
+ onMainTabChange,
+ onContractsTabChange,
+ onUtilsTabChange,
+ contractsContent,
+ utilsContent,
+}: HorizontalLayoutProps) {
+ return (
+ <>
+ {}} />
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/components/navigation/layouts/MobileLayout.tsx b/apps/frontend/src/pages/Admin/components/navigation/layouts/MobileLayout.tsx
new file mode 100644
index 00000000..1fcf4a13
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/components/navigation/layouts/MobileLayout.tsx
@@ -0,0 +1,54 @@
+import { MobileNavDrawer } from "../MobileNavDrawer";
+import { ResponsiveHeader } from "../ResponsiveHeader";
+import { MobileContent } from "../NavigationComponents";
+
+interface MobileLayoutProps {
+ mainTabIndex: number;
+ contractsTabIndex: number;
+ utilsTabIndex: number;
+ onMainTabChange: (index: number) => void;
+ onContractsTabChange: (index: number) => void;
+ onUtilsTabChange: (index: number) => void;
+ contractsContent: React.ReactNode[];
+ utilsContent: React.ReactNode[];
+ drawerState: {
+ isOpen: boolean;
+ onClose: () => void;
+ onToggle: () => void;
+ };
+}
+
+export function MobileLayout({
+ mainTabIndex,
+ contractsTabIndex,
+ utilsTabIndex,
+ onMainTabChange,
+ onContractsTabChange,
+ onUtilsTabChange,
+ contractsContent,
+ utilsContent,
+ drawerState,
+}: MobileLayoutProps) {
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+}
diff --git a/apps/frontend/src/pages/Admin/constants/contracts.ts b/apps/frontend/src/pages/Admin/constants/contracts.ts
new file mode 100644
index 00000000..797ced2f
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/constants/contracts.ts
@@ -0,0 +1,34 @@
+export type ContractType = "vevote" | "nodeManagement" | "stargate";
+
+export const CONTRACT_CONFIGS = {
+ vevote: {
+ roles: [
+ "DEFAULT_ADMIN_ROLE",
+ "UPGRADER_ROLE",
+ "WHITELIST_ADMIN_ROLE",
+ "WHITELISTED_ROLE",
+ "EXECUTOR_ROLE",
+ "SETTINGS_MANAGER_ROLE",
+ "NODE_WEIGHT_MANAGER_ROLE",
+ ],
+ addressKey: "vevoteContractAddress" as const,
+ },
+ nodeManagement: {
+ roles: [
+ "DEFAULT_ADMIN_ROLE",
+ "UPGRADER_ROLE",
+ ],
+ addressKey: "nodeManagementContractAddress" as const,
+ },
+ stargate: {
+ roles: [
+ "DEFAULT_ADMIN_ROLE",
+ "UPGRADER_ROLE",
+ "PAUSER_ROLE",
+ "LEVEL_OPERATOR_ROLE",
+ "MANAGER_ROLE",
+ "WHITELISTER_ROLE",
+ ],
+ addressKey: "stargateNFTContractAddress" as const,
+ },
+} as const;
\ No newline at end of file
diff --git a/apps/frontend/src/pages/Admin/hooks/index.ts b/apps/frontend/src/pages/Admin/hooks/index.ts
new file mode 100644
index 00000000..00e51823
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/hooks/index.ts
@@ -0,0 +1,4 @@
+export { useVeVoteInfo } from "./useVeVoteInfo";
+export { useUserNodeInfo, useNodeLevel, useIsNodeHolder } from "./useNodeManagement";
+export { useStargateStats, useStargateLevels, useTokensOwnedBy } from "./useStargateData";
+export { useResponsiveNavigation } from "./useResponsiveNavigation";
\ No newline at end of file
diff --git a/apps/frontend/src/pages/Admin/hooks/useContractAddresses.ts b/apps/frontend/src/pages/Admin/hooks/useContractAddresses.ts
new file mode 100644
index 00000000..4cce5eee
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/hooks/useContractAddresses.ts
@@ -0,0 +1,16 @@
+import { useQuery } from "@tanstack/react-query";
+import { veVoteService } from "../services";
+
+export const useContractAddresses = () => {
+ const { data, isLoading, error } = useQuery({
+ queryKey: ["contractAddresses"],
+ queryFn: async () => await veVoteService.getContractAddress(),
+ staleTime: 5 * 60 * 1000,
+ });
+
+ return {
+ contractAddresses: data,
+ isLoading,
+ error,
+ };
+};
diff --git a/apps/frontend/src/pages/Admin/hooks/useLevelMultipliersInfo.ts b/apps/frontend/src/pages/Admin/hooks/useLevelMultipliersInfo.ts
new file mode 100644
index 00000000..18fb9beb
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/hooks/useLevelMultipliersInfo.ts
@@ -0,0 +1,10 @@
+import { useQuery } from "@tanstack/react-query";
+import { veVoteService } from "../services/VeVoteService";
+
+export const useLevelMultipliersInfo = () => {
+ return useQuery({
+ queryKey: ["levelMultipliers"],
+ queryFn: () => veVoteService.getLevelMultipliers(),
+ staleTime: 1000 * 60 * 5,
+ });
+};
diff --git a/apps/frontend/src/pages/Admin/hooks/useNodeManagement.ts b/apps/frontend/src/pages/Admin/hooks/useNodeManagement.ts
new file mode 100644
index 00000000..8a291a04
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/hooks/useNodeManagement.ts
@@ -0,0 +1,29 @@
+import { useQuery } from "@tanstack/react-query";
+import { nodeManagementService } from "../services";
+
+export function useUserNodeInfo(userAddress: string) {
+ return useQuery({
+ queryKey: ["userNodeInfo", userAddress],
+ queryFn: () => nodeManagementService.getUserNodeInfo(userAddress),
+ enabled: !!userAddress.trim(),
+ staleTime: 2 * 60 * 1000,
+ });
+}
+
+export function useNodeLevel(nodeId: bigint) {
+ return useQuery({
+ queryKey: ["nodeLevel", nodeId.toString()],
+ queryFn: () => nodeManagementService.getNodeLevel(nodeId),
+ enabled: !!nodeId,
+ staleTime: 10 * 60 * 1000,
+ });
+}
+
+export function useIsNodeHolder(userAddress: string) {
+ return useQuery({
+ queryKey: ["isNodeHolder", userAddress],
+ queryFn: () => nodeManagementService.isNodeHolder(userAddress),
+ enabled: !!userAddress.trim(),
+ staleTime: 2 * 60 * 1000,
+ });
+}
diff --git a/apps/frontend/src/pages/Admin/hooks/useResponsiveNavigation.ts b/apps/frontend/src/pages/Admin/hooks/useResponsiveNavigation.ts
new file mode 100644
index 00000000..c97cfa95
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/hooks/useResponsiveNavigation.ts
@@ -0,0 +1,39 @@
+import { useBreakpointValue, useDisclosure } from "@chakra-ui/react";
+import { useMemo } from "react";
+
+export function useResponsiveNavigation() {
+ const { isOpen, onOpen, onClose, onToggle } = useDisclosure();
+
+ const layout = useBreakpointValue(
+ {
+ base: "sm",
+ md: "md",
+ lg: "lg",
+ },
+ {
+ fallback: "sm",
+ },
+ );
+
+ const resultLayout = useMemo(() => {
+ const showVerticalTabs = layout === "lg";
+ const showHorizontalTabs = layout === "md";
+ const showMobileDrawer = layout === "sm";
+ return {
+ layout,
+ showVerticalTabs,
+ showHorizontalTabs,
+ showMobileDrawer,
+ };
+ }, [layout]);
+
+ return {
+ ...resultLayout,
+ drawerState: {
+ isOpen,
+ onOpen,
+ onClose,
+ onToggle,
+ },
+ };
+}
diff --git a/apps/frontend/src/pages/Admin/hooks/useStargateData.ts b/apps/frontend/src/pages/Admin/hooks/useStargateData.ts
new file mode 100644
index 00000000..2be4b53b
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/hooks/useStargateData.ts
@@ -0,0 +1,28 @@
+import { useQuery } from "@tanstack/react-query";
+import { stargateService } from "../services";
+
+export function useStargateStats() {
+ return useQuery({
+ queryKey: ["stargateStats"],
+ queryFn: () => stargateService.getStargateStats(),
+ staleTime: 5 * 60 * 1000,
+ refetchInterval: 15 * 60 * 1000,
+ });
+}
+
+export function useStargateLevels() {
+ return useQuery({
+ queryKey: ["stargateLevels"],
+ queryFn: () => stargateService.getLevels(),
+ staleTime: 10 * 60 * 1000,
+ });
+}
+
+export function useTokensOwnedBy(owner: string) {
+ return useQuery({
+ queryKey: ["tokensOwnedBy", owner],
+ queryFn: () => stargateService.getTokensOwnedBy(owner),
+ enabled: !!owner.trim(),
+ staleTime: 2 * 60 * 1000,
+ });
+}
diff --git a/apps/frontend/src/pages/Admin/hooks/useVeVoteInfo.ts b/apps/frontend/src/pages/Admin/hooks/useVeVoteInfo.ts
new file mode 100644
index 00000000..8ee46a59
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/hooks/useVeVoteInfo.ts
@@ -0,0 +1,11 @@
+import { useQuery } from "@tanstack/react-query";
+import { veVoteService } from "../services";
+
+export function useVeVoteInfo() {
+ return useQuery({
+ queryKey: ["veVoteInfo"],
+ queryFn: async () => await veVoteService.getVeVoteInfo(),
+ staleTime: 5 * 60 * 1000, // 5 minutes
+ refetchInterval: 30 * 60 * 1000, // 30 minutes
+ });
+}
diff --git a/apps/frontend/src/pages/Admin/hooks/useVotingPowerAtTimepoint.ts b/apps/frontend/src/pages/Admin/hooks/useVotingPowerAtTimepoint.ts
new file mode 100644
index 00000000..d301ca3b
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/hooks/useVotingPowerAtTimepoint.ts
@@ -0,0 +1,45 @@
+import { useQuery } from "@tanstack/react-query";
+import { veVoteService } from "../services/VeVoteService";
+import { ZERO_ADDRESS } from "@vechain/sdk-core";
+
+export interface VotingPowerAtTimepointResult {
+ nodePower: bigint;
+ validatorPower: bigint;
+ totalPower: bigint;
+}
+
+interface UseVotingPowerAtTimepointProps {
+ address?: string;
+ timepoint?: number;
+ masterAddress?: string;
+}
+
+export const useVotingPowerAtTimepoint = ({ address, timepoint, masterAddress }: UseVotingPowerAtTimepointProps) => {
+ return useQuery({
+ queryKey: ["votingPowerAtTimepoint", address, timepoint, masterAddress],
+ queryFn: async (): Promise => {
+ if (!address || timepoint === undefined) {
+ return {
+ nodePower: BigInt(0),
+ validatorPower: BigInt(0),
+ totalPower: BigInt(0),
+ };
+ }
+
+ const [nodePower, validatorPower] = await Promise.all([
+ veVoteService.getVotingPowerAtTimepoint(address, timepoint, ZERO_ADDRESS),
+ masterAddress
+ ? veVoteService.getVotingPowerAtTimepoint(address, timepoint, masterAddress)
+ : Promise.resolve(BigInt(0)),
+ ]);
+
+ return {
+ nodePower,
+ validatorPower,
+ totalPower: nodePower + validatorPower,
+ };
+ },
+ enabled: !!address && timepoint !== undefined,
+ staleTime: 30000,
+ });
+};
diff --git a/apps/frontend/src/pages/Admin/services/NodeManagementService.ts b/apps/frontend/src/pages/Admin/services/NodeManagementService.ts
new file mode 100644
index 00000000..3e77801d
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/services/NodeManagementService.ts
@@ -0,0 +1,111 @@
+import { NodeManagement__factory } from "@vechain/vevote-contracts/typechain-types";
+import { getConfig } from "@repo/config";
+import { executeCall, executeMultipleClauses } from "../../../utils/contract";
+
+export interface NodeStats {
+ totalNodes: number;
+ delegatedNodes: number;
+ levelDistribution: { [level: number]: number };
+}
+
+export interface UserNodeInfo {
+ ownedNodes: bigint[];
+ managedNodes: bigint[];
+ isNodeHolder: boolean;
+ isNodeDelegator: boolean;
+}
+
+export class NodeManagementService {
+ private readonly contractAddress: string;
+ private readonly contractInterface: ReturnType;
+
+ constructor() {
+ const config = getConfig(import.meta.env.VITE_APP_ENV);
+ this.contractAddress = config.nodeManagementContractAddress;
+ this.contractInterface = NodeManagement__factory.createInterface();
+ }
+
+ async getUserNodeInfo(userAddress: string): Promise {
+ const methodsWithArgs = [
+ { method: "getDirectNodesOwnership" as const, args: [userAddress] },
+ { method: "getNodeIds" as const, args: [userAddress] },
+ { method: "isNodeHolder" as const, args: [userAddress] },
+ { method: "isNodeDelegator" as const, args: [userAddress] },
+ ];
+
+ const results = await executeMultipleClauses({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ methodsWithArgs,
+ });
+
+ return {
+ ownedNodes:
+ results[0]?.success && Array.isArray(results[0].result.plain)
+ ? results[0].result.plain.map((id: unknown) => BigInt(String(id)))
+ : [],
+ managedNodes:
+ results[1]?.success && Array.isArray(results[1].result.plain)
+ ? results[1].result.plain.map((id: unknown) => BigInt(String(id)))
+ : [],
+ isNodeHolder: Boolean(results[2]?.success ? results[2].result.plain : false),
+ isNodeDelegator: Boolean(results[3]?.success ? results[3].result.plain : false),
+ };
+ }
+
+ async getNodeLevel(nodeId: bigint): Promise {
+ const result = await executeCall({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ method: "getNodeLevel",
+ args: [nodeId],
+ });
+ return result?.success ? Number(result.result.plain) : 0;
+ }
+
+ async isNodeHolder(userAddress: string): Promise {
+ const result = await executeCall({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ method: "isNodeHolder",
+ args: [userAddress],
+ });
+ return Boolean(result?.success ? result.result.plain : false);
+ }
+
+ async isNodeDelegator(userAddress: string): Promise {
+ const result = await executeCall({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ method: "isNodeDelegator",
+ args: [userAddress],
+ });
+ return Boolean(result?.success ? result.result.plain : false);
+ }
+
+ async getDirectNodesOwnership(userAddress: string): Promise {
+ const result = await executeCall({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ method: "getDirectNodesOwnership",
+ args: [userAddress],
+ });
+ return result?.success && Array.isArray(result.result.plain)
+ ? result.result.plain.map((id: unknown) => BigInt(String(id)))
+ : [];
+ }
+
+ async getNodeIds(userAddress: string): Promise {
+ const result = await executeCall({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ method: "getNodeIds",
+ args: [userAddress],
+ });
+ return result?.success && Array.isArray(result.result.plain)
+ ? result.result.plain.map((id: unknown) => BigInt(String(id)))
+ : [];
+ }
+}
+
+export const nodeManagementService = new NodeManagementService();
diff --git a/apps/frontend/src/pages/Admin/services/StargateService.ts b/apps/frontend/src/pages/Admin/services/StargateService.ts
new file mode 100644
index 00000000..a897fa7f
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/services/StargateService.ts
@@ -0,0 +1,122 @@
+import { StargateNFT__factory } from "@vechain/vevote-contracts/typechain-types";
+import { getConfig } from "@repo/config";
+import { executeCall, executeMultipleClauses } from "../../../utils/contract";
+
+export interface StargateLevel {
+ name: string;
+ maturityBlocks: bigint;
+ vetAmountRequiredToStake: bigint;
+}
+
+export interface StargateLevelSupply {
+ circulating: bigint;
+ cap: number;
+}
+
+export interface StargateToken {
+ tokenId: bigint;
+ owner: string;
+ levelId: number;
+ mintTimestamp: bigint;
+}
+
+export interface StargateStats {
+ totalSupply: bigint;
+ levelIds: number[];
+ levels: StargateLevel[];
+}
+
+export class StargateService {
+ private readonly contractAddress: string;
+ private readonly contractInterface: ReturnType;
+
+ constructor() {
+ const config = getConfig(import.meta.env.VITE_APP_ENV);
+ this.contractAddress = config.stargateNFTContractAddress;
+ this.contractInterface = StargateNFT__factory.createInterface();
+ }
+
+ async getStargateStats(): Promise {
+ const levelIds = await this.getLevelIds();
+
+ const methodsWithArgs = [
+ { method: "totalSupply" as const, args: [] },
+ { method: "getLevels" as const, args: [] },
+ ];
+
+ const results = await executeMultipleClauses({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ methodsWithArgs,
+ });
+
+ const totalSupply = BigInt(results[0]?.success ? String(results[0].result.plain) : "0");
+ const levels = (results[1]?.success ? results[1].result.plain : []) as StargateLevel[];
+
+ return {
+ totalSupply,
+ levelIds,
+ levels,
+ };
+ }
+
+ async getLevelIds(): Promise {
+ const result = await executeCall({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ method: "getLevelIds",
+ args: [],
+ });
+ return result?.success && Array.isArray(result.result.plain)
+ ? result.result.plain.map((id: unknown) => Number(id))
+ : [];
+ }
+
+ async getLevels(): Promise {
+ const result = await executeCall({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ method: "getLevels",
+ args: [],
+ });
+ return result?.success && Array.isArray(result.result.plain) ? result.result.plain : [];
+ }
+
+ async getLevelSupply(levelId: number): Promise {
+ const result = await executeCall({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ method: "getLevelSupply",
+ args: [levelId],
+ });
+ if (result?.success && Array.isArray(result.result.plain)) {
+ return {
+ circulating: BigInt(String(result.result.plain[0] || "0")),
+ cap: Number(result.result.plain[1] || 0),
+ };
+ }
+ return { circulating: BigInt("0"), cap: 0 };
+ }
+
+ async getTotalSupply(): Promise {
+ const result = await executeCall({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ method: "totalSupply",
+ args: [],
+ });
+ return BigInt(result?.success ? String(result.result.plain) : "0");
+ }
+
+ async getTokensOwnedBy(owner: string): Promise {
+ const result = await executeCall({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ method: "tokensOwnedBy",
+ args: [owner],
+ });
+ return result?.success && Array.isArray(result.result.plain) ? result.result.plain : [];
+ }
+}
+
+export const stargateService = new StargateService();
diff --git a/apps/frontend/src/pages/Admin/services/VeVoteService.ts b/apps/frontend/src/pages/Admin/services/VeVoteService.ts
new file mode 100644
index 00000000..b84b3c73
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/services/VeVoteService.ts
@@ -0,0 +1,265 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import { VeVote__factory } from "@vechain/vevote-contracts";
+import { getConfig } from "@repo/config";
+import { executeCall, executeMultipleClauses } from "../../../utils/contract";
+import { ZERO_ADDRESS } from "@vechain/sdk-core";
+import { getAllEventLogs, ThorClient } from "@vechain/vechain-kit";
+import { VALIDATOR_STAKED_VET_REQUIREMENT } from "@/constants";
+import { StargateLevelRow } from "../components/Contracts/components/StargateLevelDetails";
+
+export interface VeVoteInfo {
+ quorumNumerator: bigint;
+ quorumDenominator: bigint;
+ minVotingDelay: number;
+ minVotingDuration: number;
+ maxVotingDuration: number;
+ minStakedAmount: bigint;
+ version: bigint;
+}
+
+export interface ContractAddresses {
+ vevoteContractAddress: string;
+ nodeManagementAddress: string;
+ stargateAddress: string;
+}
+
+type VevoteContractInterface = ReturnType;
+
+export interface VotingMultipliers {
+ [levelId: number]: bigint;
+}
+
+export interface RoleUserInfo {
+ address: string;
+ grantedAt: Date;
+ transactionId: string;
+ isCurrentlyGranted: boolean;
+ lastActionAt: Date;
+}
+
+export class VeVoteService {
+ private readonly contractAddress: string;
+ private readonly contractInterface: VevoteContractInterface;
+ private readonly nodeUrl: string;
+
+ constructor() {
+ const config = getConfig(import.meta.env.VITE_APP_ENV);
+ this.contractAddress = config.vevoteContractAddress;
+ this.contractInterface = VeVote__factory.createInterface();
+ this.nodeUrl = config.nodeUrl;
+ }
+
+ async getVeVoteInfo(): Promise {
+ const methodsWithArgs = [
+ { method: "quorumDenominator" as const, args: [] },
+ { method: "getMinVotingDelay" as const, args: [] },
+ { method: "getMinVotingDuration" as const, args: [] },
+ { method: "getMaxVotingDuration" as const, args: [] },
+ { method: "getMinStakedAmount" as const, args: [] },
+ { method: "version" as const, args: [] },
+ ];
+
+ const results = await executeMultipleClauses({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ methodsWithArgs,
+ });
+
+ const quorumNumerator = await this.getQuorumNumerator();
+
+ return {
+ quorumNumerator,
+ quorumDenominator: BigInt(results[0]?.success ? String(results[0].result.plain) : "0"),
+ minVotingDelay: Number(results[1]?.success ? results[1].result.plain : 0),
+ minVotingDuration: Number(results[2]?.success ? results[2].result.plain : 0),
+ maxVotingDuration: Number(results[3]?.success ? results[3].result.plain : 0),
+ minStakedAmount: BigInt(results[4]?.success ? String(results[4].result.plain) : "0"),
+ version: BigInt(results[5]?.success ? String(results[5].result.plain) : "0"),
+ };
+ }
+
+ async getContractAddress(): Promise {
+ const methodsWithArgs = [
+ { method: "getNodeManagementContract" as const, args: [] },
+ { method: "getStargateNFTContract" as const, args: [] },
+ ];
+
+ try {
+ const results = await executeMultipleClauses({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ methodsWithArgs,
+ });
+
+ return {
+ vevoteContractAddress: this.contractAddress,
+ nodeManagementAddress: results[0]?.success ? String(results[0].result.plain) : "",
+ stargateAddress: results[1]?.success ? String(results[1].result.plain) : "",
+ };
+ } catch (error) {
+ console.error("Error fetching contract addresses:", error);
+ throw error;
+ }
+ }
+
+ async getQuorumNumerator(): Promise {
+ try {
+ const result = await executeCall({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ method: "quorumNumerator()" as const,
+ args: [],
+ });
+ return result?.success ? BigInt(result.result.plain as string) : BigInt(0);
+ } catch (error) {
+ console.error("Error fetching quorum numerator:", error);
+ return BigInt(0);
+ }
+ }
+
+ async getLevelMultipliers(): Promise {
+ const DEFAULT_MULTIPLIERS = [200, 100, 100, 100, 150, 150, 150, 150, 100, 100, 100];
+ const methodsWithArgs = Array.from({ length: 11 }, (_, index) => ({
+ method: "levelIdMultiplier" as const,
+ args: [index],
+ }));
+
+ try {
+ const results = await executeMultipleClauses({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ methodsWithArgs,
+ });
+
+ return results.map(result => (result?.success ? Number(result.result.plain) : 100)) || DEFAULT_MULTIPLIERS;
+ } catch (error) {
+ console.error("Error fetching level multipliers:", error);
+ return DEFAULT_MULTIPLIERS;
+ }
+ }
+
+ async getVotingPowerAtTimepoint(address: string, timepoint: number, masterAddress?: string): Promise {
+ try {
+ const result = await executeMultipleClauses({
+ contractAddress: this.contractAddress,
+ contractInterface: this.contractInterface,
+ methodsWithArgs: [
+ {
+ method: "getVoteWeightAtTimepoint" as const,
+ args: [address, timepoint, masterAddress || ZERO_ADDRESS],
+ },
+ ],
+ });
+
+ return result[0]?.success ? BigInt(result[0].result.plain as string) : BigInt(0);
+ } catch (error) {
+ console.error("Error fetching voting power at timepoint:", error);
+ return BigInt(0);
+ }
+ }
+
+ getValidatorInfo(): StargateLevelRow {
+ return {
+ name: "Validator",
+ vetAmountRequiredToStake: VALIDATOR_STAKED_VET_REQUIREMENT,
+ levelId: 0,
+ maturityBlocks: BigInt(0),
+ };
+ }
+
+ async getRoleUsers(thor: ThorClient, roleHash: string): Promise {
+ if (!thor) {
+ return [];
+ }
+
+ try {
+ const roleGrantedAbi = thor.contracts.load(this.contractAddress, VeVote__factory.abi).getEventAbi("RoleGranted");
+ const roleRevokedAbi = thor.contracts.load(this.contractAddress, VeVote__factory.abi).getEventAbi("RoleRevoked");
+
+ const filterCriteria = [
+ {
+ criteria: {
+ address: this.contractAddress,
+ topic0: roleGrantedAbi.signatureHash,
+ topic1: roleHash,
+ },
+ eventAbi: roleGrantedAbi,
+ },
+ {
+ criteria: {
+ address: this.contractAddress,
+ topic0: roleRevokedAbi.signatureHash,
+ topic1: roleHash,
+ },
+ eventAbi: roleRevokedAbi,
+ },
+ ];
+
+ const allEvents = await getAllEventLogs({ thor, nodeUrl: this.nodeUrl, filterCriteria });
+
+ const userEventsMap = new Map<
+ string,
+ Array<{
+ type: "granted" | "revoked";
+ timestamp: number;
+ blockNumber: number;
+ transactionId: string;
+ grantedAt?: Date;
+ }>
+ >();
+
+ allEvents.forEach(event => {
+ if (event.decodedData) {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [_role, account, _sender] = event.decodedData as [string, string, string];
+
+ if (!userEventsMap.has(account)) {
+ userEventsMap.set(account, []);
+ }
+
+ const eventType = event.topics[0] === roleGrantedAbi.signatureHash ? "granted" : "revoked";
+
+ userEventsMap.get(account)!.push({
+ type: eventType,
+ timestamp: event.meta.blockTimestamp,
+ blockNumber: event.meta.blockNumber,
+ transactionId: event.meta.txID,
+ grantedAt: eventType === "granted" ? new Date(event.meta.blockTimestamp * 1000) : undefined,
+ });
+ }
+ });
+
+ const currentUsers: RoleUserInfo[] = [];
+
+ userEventsMap.forEach((events, address) => {
+ events.sort((a, b) => {
+ if (a.blockNumber !== b.blockNumber) {
+ return a.blockNumber - b.blockNumber;
+ }
+ return a.timestamp - b.timestamp;
+ });
+
+ const lastEvent = events[events.length - 1];
+
+ if (lastEvent.type === "granted") {
+ const currentGrantEvent = lastEvent;
+
+ currentUsers.push({
+ address,
+ grantedAt: new Date(currentGrantEvent.timestamp * 1000),
+ transactionId: currentGrantEvent.transactionId,
+ isCurrentlyGranted: true,
+ lastActionAt: new Date(lastEvent.timestamp * 1000),
+ });
+ }
+ });
+
+ return currentUsers.sort((a, b) => b.lastActionAt.getTime() - a.lastActionAt.getTime());
+ } catch (error) {
+ console.error("Error fetching role users:", error);
+ return [];
+ }
+ }
+}
+
+export const veVoteService = new VeVoteService();
diff --git a/apps/frontend/src/pages/Admin/services/index.ts b/apps/frontend/src/pages/Admin/services/index.ts
new file mode 100644
index 00000000..4e4b5fd6
--- /dev/null
+++ b/apps/frontend/src/pages/Admin/services/index.ts
@@ -0,0 +1,15 @@
+export { VeVoteService, veVoteService, type VeVoteInfo } from "./VeVoteService";
+export {
+ NodeManagementService,
+ nodeManagementService,
+ type UserNodeInfo,
+ type NodeStats,
+} from "./NodeManagementService";
+export {
+ StargateService,
+ stargateService,
+ type StargateLevel,
+ type StargateLevelSupply,
+ type StargateToken,
+ type StargateStats,
+} from "./StargateService";
diff --git a/apps/frontend/src/schema/adminSchema.ts b/apps/frontend/src/schema/adminSchema.ts
new file mode 100644
index 00000000..6c448ee6
--- /dev/null
+++ b/apps/frontend/src/schema/adminSchema.ts
@@ -0,0 +1,42 @@
+import { z } from "zod";
+import { addressSchema, requiredString } from "@/utils/zod";
+
+export const userManagementSchema = z.object({
+ userAddress: addressSchema,
+ selectedRole: requiredString,
+});
+
+export type UserManagementSchema = z.infer;
+
+export const nodeInfoSchema = z.object({
+ userAddress: addressSchema,
+});
+
+export type NodeInfoSchema = z.infer;
+
+export const governanceSettingsSchema = z.object({
+ quorumNumerator: z.literal("").or(z.coerce.number().min(1).max(100)),
+ minVotingDelay: z.literal("").or(z.coerce.number().min(1).max(259200)),
+ minVotingDuration: z.literal("").or(z.coerce.number().min(1).max(259200)),
+ maxVotingDuration: z.literal("").or(z.coerce.number().min(1).max(259200)),
+ minStakedVetAmount: z.literal("").or(z.coerce.number().min(0.001).max(1000000)),
+});
+
+export type GovernanceSettingsSchema = z.infer;
+
+export const votingPowerQuerySchema = z.object({
+ address: addressSchema,
+ timepoint: z.coerce.number().min(0),
+ masterAddress: z
+ .string()
+ .optional()
+ .refine(val => !val || /^0x[a-fA-F0-9]{40}$/.test(val)),
+});
+
+export type VotingPowerQuerySchema = z.infer;
+
+export const levelMultipliersSchema = z.object({
+ multipliers: z.array(z.coerce.number().min(0).max(1000)).length(11),
+});
+
+export type LevelMultipliersSchema = z.infer;
diff --git a/apps/frontend/src/types/routes.ts b/apps/frontend/src/types/routes.ts
index 2f83aebb..f801daef 100644
--- a/apps/frontend/src/types/routes.ts
+++ b/apps/frontend/src/types/routes.ts
@@ -2,4 +2,5 @@ export enum Routes {
HOME = "/",
PROPOSAL = "/proposal",
CREATE_PROPOSAL = "/create-proposal",
+ ADMIN = "/admin",
}
diff --git a/apps/frontend/src/utils/proposals/helpers.ts b/apps/frontend/src/utils/proposals/helpers.ts
index 0b703df8..0b2a9c10 100644
--- a/apps/frontend/src/utils/proposals/helpers.ts
+++ b/apps/frontend/src/utils/proposals/helpers.ts
@@ -272,3 +272,7 @@ export const parseHistoricalProposals = async (
};
});
};
+
+export const formatVotingPower = (power: bigint): string => {
+ return (Number(power) / 100).toString();
+};
diff --git a/apps/frontend/src/utils/zod.ts b/apps/frontend/src/utils/zod.ts
index c949b3b1..4ef7ef31 100644
--- a/apps/frontend/src/utils/zod.ts
+++ b/apps/frontend/src/utils/zod.ts
@@ -61,3 +61,11 @@ export const zodFile = z.object({
});
export type ZodFile = z.infer;
+
+export const isValidAddress = (address: string): boolean => {
+ return /^0x[a-fA-F0-9]{40}$/.test(address);
+};
+
+export const addressSchema = z.string().trim().min(1, { message: LL.field_errors.required() }).refine(isValidAddress, {
+ message: LL.field_errors.invalid_address(),
+});