diff --git a/components/tools/common/api/validator-info.ts b/components/tools/common/api/validator-info.ts index d5ece29657c..3d8dd606fb4 100644 --- a/components/tools/common/api/validator-info.ts +++ b/components/tools/common/api/validator-info.ts @@ -2,6 +2,7 @@ import { Validator, SubnetInfo, L1ValidatorManagerDetails } from './types'; import { pChainEndpoint } from './consts'; import { AvaCloudSDK } from "@avalabs/avacloud-sdk"; export const avaCloudSDK = new AvaCloudSDK({ + serverURL: "https://api.avax.network", chainId: "43114", network: "fuji", }); diff --git a/package.json b/package.json index 91a1b96a275..4a6f6baa817 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "postinstall": "prisma generate && fumadocs-mdx && cd toolbox && yarn" }, "dependencies": { - "@avalabs/avacloud-sdk": "0.8.7", + "@avalabs/avacloud-sdk": "0.12.1", "@avalabs/avalanchejs": "^5.0.0", "@fumadocs/mdx-remote": "^1.2.0", "@hookform/resolvers": "^4.1.3", @@ -128,4 +128,4 @@ "typescript": "^5.8.2" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" -} +} \ No newline at end of file diff --git a/toolbox/package.json b/toolbox/package.json index fce5d3ec7b0..ead300e0986 100644 --- a/toolbox/package.json +++ b/toolbox/package.json @@ -26,7 +26,7 @@ "postinstall": "node update_docker_tags.js" }, "dependencies": { - "@avalabs/avacloud-sdk": "^0.9.0", + "@avalabs/avacloud-sdk": "^0.12.1", "@avalabs/avalanchejs": "^4.1.2-alpha.4", "@radix-ui/react-alert-dialog": "^1.1.13", "@radix-ui/react-checkbox": "^1.2.3", @@ -72,4 +72,4 @@ "vite": "^6.1.6", "vite-plugin-dts": "^4.5.0" } -} +} \ No newline at end of file diff --git a/toolbox/src/components/InputSubnetId.tsx b/toolbox/src/components/InputSubnetId.tsx new file mode 100644 index 00000000000..124f195fb82 --- /dev/null +++ b/toolbox/src/components/InputSubnetId.tsx @@ -0,0 +1,142 @@ +"use client" + +import { Input, type Suggestion } from "./Input"; +import { useL1ListStore } from "../stores/l1ListStore"; +import { useCreateChainStore } from "../stores/createChainStore"; +import { useWalletStore } from "../stores/walletStore"; +import { useMemo, useState, useCallback, useEffect } from "react"; +import { AvaCloudSDK } from "@avalabs/avacloud-sdk"; +import { networkIDs } from "@avalabs/avalanchejs"; +import { GlobalParamNetwork } from "@avalabs/avacloud-sdk/models/components"; + +// Primary network subnet ID +const PRIMARY_NETWORK_SUBNET_ID = "11111111111111111111111111111111LpoYY"; + +export default function InputSubnetId({ + value, + onChange, + error, + label = "Subnet ID", + hidePrimaryNetwork = false, + helperText, + id +}: { + value: string, + onChange: (value: string) => void, + error?: string | null, + label?: string, + hidePrimaryNetwork?: boolean + helperText?: string | null + id?: string +}) { + const { avalancheNetworkID } = useWalletStore(); + const createChainStoreSubnetId = useCreateChainStore()(state => state.subnetId); + const { l1List } = useL1ListStore()(); + + const [validationError, setValidationError] = useState(null); + + // Network names for API calls + const networkNames: Record = { + [networkIDs.MainnetID]: "mainnet", + [networkIDs.FujiID]: "fuji", + }; + + // Validate subnet ID using AvaCloud SDK + const validateSubnetId = useCallback(async (subnetId: string) => { + if (!subnetId || subnetId.length < 10) { + setValidationError(null); + return; + } + + try { + setValidationError(null); + + const network = networkNames[Number(avalancheNetworkID)]; + if (!network) { + setValidationError(null); + return; + } + + const sdk = new AvaCloudSDK({ + serverURL: "https://api.avax.network", + network: network, + }); + + await sdk.data.primaryNetwork.getSubnetById({ + network: network, + subnetId, + }); + + // If we get here, the subnet exists + setValidationError(null); + } catch (error) { + // Show validation error for invalid subnet IDs + setValidationError("Subnet ID not found or invalid"); + } + }, [avalancheNetworkID, networkNames]); + + // Validate when value changes + useEffect(() => { + const timeoutId = setTimeout(() => { + if (value) { + validateSubnetId(value); + } else { + setValidationError(null); + } + }, 500); // Debounce validation + + return () => clearTimeout(timeoutId); + }, [value, validateSubnetId]); + + const subnetIdSuggestions: Suggestion[] = useMemo(() => { + const result: Suggestion[] = []; + const seen = new Set(); + + // Add subnet from create chain store first + if (createChainStoreSubnetId && !(hidePrimaryNetwork && createChainStoreSubnetId === PRIMARY_NETWORK_SUBNET_ID)) { + result.push({ + title: createChainStoreSubnetId, + value: createChainStoreSubnetId, + description: "The Subnet that you have just created in the \"Create Chain\" tool" + }); + seen.add(createChainStoreSubnetId); + } + + // Add subnets from L1 list + for (const l1 of l1List) { + const { subnetId, name } = l1; + + if (!subnetId || seen.has(subnetId)) continue; + + if (hidePrimaryNetwork && subnetId === PRIMARY_NETWORK_SUBNET_ID) { + continue; + } + + result.push({ + title: `${name} (${subnetId})`, + value: subnetId, + description: l1.description || "A subnet that was added to your L1 list.", + }); + + seen.add(subnetId); + } + + return result; + }, [createChainStoreSubnetId, l1List, hidePrimaryNetwork]); + + // Combine validation error with passed error + const combinedError = error || validationError; + + return ( + + ); +} \ No newline at end of file diff --git a/toolbox/src/components/SelectSubnet.tsx b/toolbox/src/components/SelectSubnet.tsx new file mode 100644 index 00000000000..f3401e03384 --- /dev/null +++ b/toolbox/src/components/SelectSubnet.tsx @@ -0,0 +1,102 @@ +"use client" + +import SelectSubnetId from "./SelectSubnetId"; +import { useState, useCallback } from "react"; +import { AvaCloudSDK } from "@avalabs/avacloud-sdk"; +import { useWalletStore } from "../stores/walletStore"; +import { networkIDs } from "@avalabs/avalanchejs"; +import { GlobalParamNetwork, Subnet } from "@avalabs/avacloud-sdk/models/components"; +import SubnetDetailsDisplay from "./SubnetDetailsDisplay"; + +export type SubnetSelection = { + subnetId: string; + subnet: Subnet | null; +} + +export default function SelectSubnet({ + value, + onChange, + error, + onlyNotConverted = false, + hidePrimaryNetwork = false +}: { + value: string, + onChange: (selection: SubnetSelection) => void, + error?: string | null, + onlyNotConverted?: boolean, + hidePrimaryNetwork?: boolean +}) { + const { avalancheNetworkID } = useWalletStore(); + const [subnetDetails, setSubnetDetails] = useState>({}); + const [isLoading, setIsLoading] = useState(false); + + // Network names for API calls + const networkNames: Record = { + [networkIDs.MainnetID]: "mainnet", + [networkIDs.FujiID]: "fuji", + }; + + // Fetch subnet details when needed + const fetchSubnetDetails = useCallback(async (subnetId: string) => { + if (!subnetId || subnetDetails[subnetId]) return; + + try { + const network = networkNames[Number(avalancheNetworkID)]; + if (!network) return; + + setIsLoading(true); + const sdk = new AvaCloudSDK({ + serverURL: "https://api.avax.network", + network: network, + }); + + const subnet = await sdk.data.primaryNetwork.getSubnetById({ + network: network, + subnetId, + }); + + setSubnetDetails(prev => ({ + ...prev, + [subnetId]: subnet + })); + } catch (error) { + console.error(`Error fetching subnet details for ${subnetId}:`, error); + } finally { + setIsLoading(false); + } + }, [avalancheNetworkID, networkNames, subnetDetails]); + + // Handle value change and fetch details if needed + const handleValueChange = useCallback((newValue: string) => { + if (newValue && !subnetDetails[newValue]) { + fetchSubnetDetails(newValue); + } + + onChange({ + subnetId: newValue, + subnet: subnetDetails[newValue] || null + }); + }, [fetchSubnetDetails, subnetDetails, onChange]); + + // Get current subnet details for display + const currentSubnet = value ? subnetDetails[value] || null : null; + const isLoadingCurrent = value && !subnetDetails[value] && isLoading; + + return ( +
+ + + {/* Display subnet details when a subnet is selected */} + {value && } +
+ ); +} \ No newline at end of file diff --git a/toolbox/src/components/SelectSubnetId.tsx b/toolbox/src/components/SelectSubnetId.tsx index 948bd742cf6..37720a3cd74 100644 --- a/toolbox/src/components/SelectSubnetId.tsx +++ b/toolbox/src/components/SelectSubnetId.tsx @@ -1,17 +1,45 @@ +import InputSubnetId from "./InputSubnetId"; import { Input, type Suggestion } from "./Input"; import { useCreateChainStore } from "../stores/createChainStore"; import { useL1ListStore } from "../stores/l1ListStore"; import { useMemo } from "react"; -export default function InputSubnetId({ value, onChange, error, onlyNotConverted = false, hidePrimaryNetwork = false }: { value: string, onChange: (value: string) => void, error?: string | null, onlyNotConverted?: boolean, hidePrimaryNetwork?: boolean }) { +export default function SelectSubnetId({ + value, + onChange, + error, + onlyNotConverted = false, + hidePrimaryNetwork = false, + label = "Subnet ID", + helperText, + id +}: { + value: string, + onChange: (value: string) => void, + error?: string | null, + onlyNotConverted?: boolean, + hidePrimaryNetwork?: boolean, + label?: string, + helperText?: string | null, + id?: string +}) { + // This component adds filtering logic on top of InputSubnetId + // If onlyNotConverted is true, it filters out converted subnets + const createChainStoreSubnetId = useCreateChainStore()(state => state.subnetId); const l1List = useL1ListStore()(state => state.l1List); - const subnetIdSuggestions: Suggestion[] = useMemo(() => { + // Create filtered suggestions if onlyNotConverted is true + const filteredSuggestions: Suggestion[] | undefined = useMemo(() => { + if (!onlyNotConverted) { + // If no filtering needed, let InputSubnetId handle suggestions + return undefined; + } + const result: Suggestion[] = []; const seen = new Set(); const PRIMARY_NETWORK_ID = "11111111111111111111111111111111LpoYY"; - + if (createChainStoreSubnetId) { result.push({ title: createChainStoreSubnetId, @@ -20,37 +48,57 @@ export default function InputSubnetId({ value, onChange, error, onlyNotConverted }); seen.add(createChainStoreSubnetId); } - + for (const l1 of l1List) { const { subnetId, name, validatorManagerAddress } = l1; - + if (!subnetId || seen.has(subnetId)) continue; - + const isPrimary = subnetId === PRIMARY_NETWORK_ID; const isConverted = !!validatorManagerAddress; - + + // Filter out converted subnets when onlyNotConverted is true if ((onlyNotConverted && (isPrimary || isConverted)) || (hidePrimaryNetwork && isPrimary)) { continue; } - + result.push({ title: `${name} (${subnetId})`, value: subnetId, description: l1.description || 'A chain that was added to your L1 list.', }); - + seen.add(subnetId); } - + return result; }, [createChainStoreSubnetId, l1List, onlyNotConverted, hidePrimaryNetwork]); - - - return + + // If we need custom filtering, use Input directly with filtered suggestions + // Otherwise, use InputSubnetId which has its own suggestions and validation + if (onlyNotConverted && filteredSuggestions) { + return ( + + ); + } + + return ( + + ); } diff --git a/toolbox/src/components/SelectValidationID.tsx b/toolbox/src/components/SelectValidationID.tsx index a45df13ff7e..b0349c4e747 100644 --- a/toolbox/src/components/SelectValidationID.tsx +++ b/toolbox/src/components/SelectValidationID.tsx @@ -43,16 +43,16 @@ export type ValidationSelection = { * @param props.subnetId - Optional subnet ID to filter validators * @param props.format - Format for validation ID: "cb58" (default) or "hex" */ -export default function SelectValidationID({ - value, - onChange, +export default function SelectValidationID({ + value, + onChange, error, subnetId = "", - format = "cb58" -}: { - value: string, - onChange: (selection: ValidationSelection) => void, - error?: string | null, + format = "cb58" +}: { + value: string, + onChange: (selection: ValidationSelection) => void, + error?: string | null, subnetId?: string, format?: "cb58" | "hex" }) { @@ -71,13 +71,18 @@ export default function SelectValidationID({ useEffect(() => { const fetchValidators = async () => { if (!subnetId) return; - + setIsLoading(true); try { const network = networkNames[Number(avalancheNetworkID)]; if (!network) return; - const result = await new AvaCloudSDK().data.primaryNetwork.listL1Validators({ + const sdk = new AvaCloudSDK({ + serverURL: "https://api.avax.network", + network: network, + }); + + const result = await sdk.data.primaryNetwork.listL1Validators({ network: network, subnetId: subnetId, }); @@ -87,7 +92,7 @@ export default function SelectValidationID({ for await (const page of result) { validatorsList.push(...page.result.validators); } - + setValidators(validatorsList); // Create a mapping of validation IDs to node IDs @@ -117,10 +122,10 @@ export default function SelectValidationID({ // Get the currently selected node ID const selectedNodeId = useMemo(() => { - return validationIdToNodeId[value] || - (value && value.startsWith("0x") && validationIdToNodeId[value]) || - (value && !value.startsWith("0x") && validationIdToNodeId["0x" + cb58ToHex(value)]) || - ""; + return validationIdToNodeId[value] || + (value && value.startsWith("0x") && validationIdToNodeId[value]) || + (value && !value.startsWith("0x") && validationIdToNodeId["0x" + cb58ToHex(value)]) || + ""; }, [value, validationIdToNodeId]); const validationIDSuggestions: Suggestion[] = useMemo(() => { @@ -133,7 +138,7 @@ export default function SelectValidationID({ const nodeId = validator.nodeId; const weightDisplay = validator.weight.toLocaleString(); const isSelected = nodeId === selectedNodeId; - + // Add just one version based on the format prop if (format === "hex") { try { @@ -180,11 +185,11 @@ export default function SelectValidationID({ // Look up the nodeId for this validation ID let nodeId = validationIdToNodeId[formattedValue] || ""; - + // If not found directly, try the alternate format if (!nodeId) { - const alternateFormat = format === "hex" - ? hexToCB58(formattedValue.slice(2)) + const alternateFormat = format === "hex" + ? hexToCB58(formattedValue.slice(2)) : "0x" + cb58ToHex(formattedValue); nodeId = validationIdToNodeId[alternateFormat] || ""; } diff --git a/toolbox/src/components/SubnetDetailsDisplay.tsx b/toolbox/src/components/SubnetDetailsDisplay.tsx new file mode 100644 index 00000000000..2a644472e11 --- /dev/null +++ b/toolbox/src/components/SubnetDetailsDisplay.tsx @@ -0,0 +1,287 @@ +"use client" + +import type { Subnet } from "@avalabs/avacloud-sdk/models/components" +import { Calendar, Users, Database, Key, Copy, AlertTriangle, FileText } from "lucide-react" +import { useState } from "react" + +interface SubnetDetailsDisplayProps { + subnet: Subnet | null + isLoading?: boolean +} + +export default function SubnetDetailsDisplay({ subnet, isLoading }: SubnetDetailsDisplayProps) { + const [copiedText, setCopiedText] = useState(null) + + // Standard EVM VM ID + const STANDARD_EVM_VM_ID = "srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy" + + if (isLoading) { + return ( +
+
+
+
+ Loading subnet details... +
+
+
+ ) + } + + if (!subnet) { + return null + } + + const formatTimestamp = (timestamp: number) => { + const date = new Date(timestamp * 1000) + const now = new Date() + + if (date > now && date.getFullYear() > now.getFullYear() + 1) { + return new Date(timestamp).toLocaleDateString("en-US", { + year: "2-digit", + month: "short", + day: "numeric", + }) + } + + return date.toLocaleDateString("en-US", { + year: "2-digit", + month: "short", + day: "numeric", + }) + } + + const copyToClipboard = (text: string | undefined | null) => { + if (!text) return + navigator.clipboard.writeText(text) + setCopiedText(text) + setTimeout(() => setCopiedText(null), 2000) + } + + return ( +
+ {/* Lighter Gray Header */} +
+
+

Subnet Details

+ + {subnet.isL1 ? "L1 Chain" : "Subnet"} + +
+
+ + {/* White Content Area */} +
+ {/* Basic Information */} +
+ {/* Left Column - Created & Chains */} +
+
+ + Created: + {formatTimestamp(subnet.createBlockTimestamp)} +
+ +
+ + Chains: + {subnet.blockchains?.length || 0} +
+
+ + {/* Right Column - Owner Address & Threshold */} +
+ {subnet.subnetOwnershipInfo.addresses && subnet.subnetOwnershipInfo.addresses.length > 0 && ( +
+
+ + Owner Address: +
+ + {subnet.subnetOwnershipInfo.addresses[0]} + + +
+
+ +
+ {subnet.subnetOwnershipInfo.threshold} of {subnet.subnetOwnershipInfo.addresses.length} signatures required +
+
+ )} +
+
+ + {/* Additional Owner Addresses if more than one */} + {subnet.subnetOwnershipInfo.addresses && subnet.subnetOwnershipInfo.addresses.length > 1 && ( +
+ Additional Owners: +
+ {subnet.subnetOwnershipInfo.addresses.slice(1, 3).map((address, index) => ( +
+ + {address} + + +
+ ))} + {subnet.subnetOwnershipInfo.addresses.length > 3 && ( + + +{subnet.subnetOwnershipInfo.addresses.length - 3} more + + )} +
+
+ )} + + {/* Blockchain Information */} + {subnet.blockchains && subnet.blockchains.length > 0 && ( +
+
+ + Blockchain Details: +
+ +
+ {subnet.blockchains.map((blockchain, index) => { + const blockchainAny = blockchain as any + const isNonStandardVM = blockchainAny.vmId && blockchainAny.vmId !== STANDARD_EVM_VM_ID + + return ( +
+
+ {blockchainAny.blockchainName || "Blockchain"} +
+ +
+ {blockchainAny.evmChainId && ( +
+ EVM Chain ID: +
+ + {blockchainAny.evmChainId} + + +
+
+ )} + +
+ Blockchain ID: +
+ + {blockchainAny.blockchainId} + + +
+
+ +
+ VM ID: +
+ + {blockchainAny.vmId} + + +
+
+
+ + {/* Warning for non-standard VM */} + {isNonStandardVM && ( +
+ + Non-standard VM detected +
+ )} +
+ ) + })} +
+
+ )} + + {/* L1 Conversion Information */} + {subnet.isL1 && subnet.l1ValidatorManagerDetails && ( +
+
+ + L1 Conversion: +
+ +
+ {subnet.l1ConversionTransactionHash && ( +
+ Conversion Tx: +
+ + {subnet.l1ConversionTransactionHash} + + +
+
+ )} + +
+ Validator Manager: +
+ + {(subnet.l1ValidatorManagerDetails as any).contractAddress} + + +
+
+
+
+ )} + + {/* Copy feedback */} + {copiedText && ( +
+ Copied to clipboard! +
+ )} +
+
+ ) +} diff --git a/toolbox/src/toolbox/L1/CollectConversionSignatures.tsx b/toolbox/src/toolbox/L1/CollectConversionSignatures.tsx index b4a98281d9c..9dccd096937 100644 --- a/toolbox/src/toolbox/L1/CollectConversionSignatures.tsx +++ b/toolbox/src/toolbox/L1/CollectConversionSignatures.tsx @@ -9,8 +9,16 @@ import { CodeHighlighter } from "../../components/CodeHighlighter"; import { Container } from "../../components/Container"; import { Input } from "../../components/Input"; import InputChainId from "../../components/InputChainId"; +import InputSubnetId from "../../components/InputSubnetId"; import { getBlockchainInfo, getSubnetInfo } from "../../coreViem/utils/glacier"; import { ResultField } from "../../components/ResultField"; +import { GlobalParamNetwork } from "@avalabs/avacloud-sdk/models/components"; + +// Network names for API calls +const networkNames: Record = { + [networkIDs.MainnetID]: "mainnet", + [networkIDs.FujiID]: "fuji", +}; export default function CollectConversionSignatures() { const { coreWalletClient } = useWalletStore(); @@ -47,18 +55,16 @@ export default function CollectConversionSignatures() { try { const { message, justification, signingSubnetId, networkId } = await coreWalletClient.extractWarpMessageFromPChainTx({ txId: conversionID }); - const { signedMessage } = await new AvaCloudSDK().data.signatureAggregator.aggregateSignatures({ - network: networkId === networkIDs.FujiID ? "fuji" : "mainnet", + const { signedMessage } = await new AvaCloudSDK({ + serverURL: "https://api.avax.network", + network: networkNames[Number(networkId)], + }).data.signatureAggregator.aggregate({ + network: networkNames[Number(networkId)], signatureAggregatorRequest: { message: message, justification: justification, signingSubnetId: signingSubnetId, - quorumPercentage: 67, // Default threshold for subnet validation - }, - }, { - retries: { - strategy: "backoff", - backoff: { initialInterval: 1000, maxInterval: 10000, exponent: 1.5, maxElapsedTime: 30 * 1000 }, + quorumPercentage: 67, } }); @@ -81,10 +87,9 @@ export default function CollectConversionSignatures() { onChange={setChainID} error={chainIdError} /> - ({ + subnetId: storeSubnetId, + subnet: null + }); const [chainID, setChainID] = useState(storeChainID); const [isConverting, setIsConverting] = useState(false); const [validators, setValidators] = useState([]); @@ -59,7 +62,7 @@ export default function ConvertToL1() { try { const txID = await coreWalletClient.convertToL1({ managerAddress, - subnetId: subnetId, + subnetId: selection.subnetId, chainId: chainID, subnetAuth: [0], validators @@ -79,9 +82,9 @@ export default function ConvertToL1() { description="This will convert your subnet to an L1 chain." >
- @@ -120,10 +123,10 @@ export default function ConvertToL1() {
diff --git a/toolbox/src/toolbox/L1/CreateChain.tsx b/toolbox/src/toolbox/L1/CreateChain.tsx index cadc1876ec2..74f41852e22 100644 --- a/toolbox/src/toolbox/L1/CreateChain.tsx +++ b/toolbox/src/toolbox/L1/CreateChain.tsx @@ -12,6 +12,7 @@ import { Step, Steps } from "fumadocs-ui/components/steps"; import generateName from 'boring-name-generator' import { Success } from "../../components/Success"; import { RadioGroup } from "../../components/RadioGroup"; +import InputSubnetId from "../../components/InputSubnetId"; export const EVM_VM_ID = "srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy" @@ -40,16 +41,20 @@ export default function CreateChain() { const [isCreatingSubnet, setIsCreatingSubnet] = useState(false); const [createdSubnetId, setCreatedSubnetId] = useState(""); - + const [isCreatingChain, setIsCreatingChain] = useState(false); const [createdChainId, setCreatedChainId] = useState(""); - + const [localGenesisData, setLocalGenesisData] = useState(genesisData); const [localChainName, setLocalChainName] = useState(generateRandomName()); const [showVMIdInput, setShowVMIdInput] = useState(false); const [vmId, setVmId] = useState(EVM_VM_ID); + // Wrapper function to handle subnet ID changes properly + const handleSubnetIdChange = (newSubnetId: string) => { + setSubnetID(newSubnetId); + }; async function handleCreateSubnet() { setIsCreatingSubnet(true); @@ -121,10 +126,12 @@ export default function CreateChain() { {createdSubnetId && ( - +
+ +
)} @@ -133,12 +140,11 @@ export default function CreateChain() { Enter the parameters for your new chain.

- -
- {createdChainId && } diff --git a/toolbox/src/toolbox/L1/QueryL1Details.tsx b/toolbox/src/toolbox/L1/QueryL1Details.tsx index 08251705948..0840f4ce509 100644 --- a/toolbox/src/toolbox/L1/QueryL1Details.tsx +++ b/toolbox/src/toolbox/L1/QueryL1Details.tsx @@ -1,75 +1,31 @@ -"use client" +"use client"; -import { useWalletStore } from "../../stores/walletStore" import { useState, useEffect } from "react" import { AlertCircle, Info, CheckCircle, - Loader2, Clock, Users, Database, ExternalLink, } from "lucide-react" -import { networkIDs } from "@avalabs/avalanchejs" -import { Button } from "../../components/Button" import { Container } from "../../components/Container" -import { AvaCloudSDK } from "@avalabs/avacloud-sdk" -import { GlobalParamNetwork, Subnet } from "@avalabs/avacloud-sdk/models/components" -import SelectSubnetId from "../../components/SelectSubnetId" +import SelectSubnet, { SubnetSelection } from "../../components/SelectSubnet" export default function QueryL1Details() { - const [subnetId, setSubnetID] = useState("") - const { avalancheNetworkID } = useWalletStore() - const [subnetDetails, setSubnetDetails] = useState(null) - const [isLoading, setIsLoading] = useState(false) + const [selection, setSelection] = useState({ subnetId: '', subnet: null }) const [error, setError] = useState(null) - const [success, setSuccess] = useState(false) - // Network names for display - const networkNames: Record = { - [networkIDs.MainnetID]: "mainnet", - [networkIDs.FujiID]: "fuji", - } + // Update error state when subnet details change useEffect(() => { - if (subnetId) { - fetchSubnetDetails() - } - }, [subnetId, avalancheNetworkID]) - - async function fetchSubnetDetails() { - if (!subnetId) { - setError("Please enter a subnet ID") - return + if (selection.subnetId && !selection.subnet) { + setError("Failed to fetch subnet details") + } else { + setError(null) } - - setIsLoading(true) - setError(null) - setSuccess(false) - - try { - const network = networkNames[Number(avalancheNetworkID)] - if (!network) { - throw new Error("Invalid network selected") - } - - const subnet = await new AvaCloudSDK().data.primaryNetwork.getSubnetById({ - network: network, - subnetId, - }); - - setSubnetDetails(subnet) - setSuccess(true) - } catch (error: any) { - setError(error.message || "Failed to fetch subnet details") - setSubnetDetails(null) - console.error("Error fetching subnet details:", error) - } finally { - setIsLoading(false) - } - } + }, [selection]) function formatTimestamp(timestamp: number): string { return new Date(timestamp * 1000).toLocaleString() @@ -90,43 +46,16 @@ export default function QueryL1Details() { )} - {success && !subnetDetails && ( -
-
- - Subnet details retrieved successfully -
-
- )} -
-
- - - {subnetDetails && ( + {selection.subnet && (
{/* Background gradient effect - blue for both light and dark mode */} @@ -144,7 +73,7 @@ export default function QueryL1Details() {
Subnet ID: - {subnetDetails.subnetId} + {selection.subnet.subnetId}
@@ -152,16 +81,16 @@ export default function QueryL1Details() {
- {subnetDetails.isL1 ? "Sovereign L1" : "Subnet"} + {selection.subnet.isL1 ? "Sovereign L1" : "Subnet"}
@@ -182,13 +111,13 @@ export default function QueryL1Details() {
Created:

- {formatTimestamp(subnetDetails.createBlockTimestamp)} + {formatTimestamp(selection.subnet.createBlockTimestamp)}

Block Index:

- {subnetDetails.createBlockIndex} + {selection.subnet.createBlockIndex}

@@ -196,7 +125,7 @@ export default function QueryL1Details() { {/* L1 Specific Information */} - {subnetDetails.isL1 && ( + {selection.subnet.isL1 && (
@@ -208,22 +137,22 @@ export default function QueryL1Details() {
- {subnetDetails.l1ValidatorManagerDetails && ( + {selection.subnet.l1ValidatorManagerDetails && (
Validator Manager Blockchain ID:

- {subnetDetails.l1ValidatorManagerDetails.blockchainId} + {selection.subnet.l1ValidatorManagerDetails.blockchainId}

Validator Manager Contract Address:

- {subnetDetails.l1ValidatorManagerDetails.contractAddress} + {selection.subnet.l1ValidatorManagerDetails.contractAddress}

)} - {subnetDetails.l1ConversionTransactionHash && ( + {selection.subnet.l1ConversionTransactionHash && (
L1 Conversion P-Chain Transaction ID: @@ -293,7 +222,7 @@ export default function QueryL1Details() {
Owner Addresses:
- {subnetDetails.subnetOwnershipInfo.addresses.map((address, index) => ( + {selection.subnet.subnetOwnershipInfo.addresses.map((address, index) => (
{/* Blockchains */} - {subnetDetails.blockchains && subnetDetails.blockchains.length > 0 && ( + {selection.subnet.blockchains && selection.subnet.blockchains.length > 0 && (
@@ -318,14 +247,14 @@ export default function QueryL1Details() {
- {subnetDetails.blockchains.length} + {selection.subnet.blockchains.length}
- {subnetDetails.blockchains.map((blockchain, index) => ( + {selection.subnet.blockchains.map((blockchain, index) => (
(null); + const [isLoading, setIsLoading] = useState(false); const [isRPC, setIsRPC] = useState(true); const [rpcCommand, setRpcCommand] = useState(""); const [nodeRunningMode, setNodeRunningMode] = useState("server"); @@ -177,10 +181,35 @@ export default function AvalanchegoDocker() { const [subnetIdError, setSubnetIdError] = useState(null); const [isAddChainModalOpen, setIsAddChainModalOpen] = useState(false); const [chainAddedToWallet, setChainAddedToWallet] = useState(null); - + const { avalancheNetworkID } = useWalletStore(); const { addL1 } = useL1ListStore()(); + useEffect(() => { + setSubnetIdError(null); + setSubnetId(""); + setSubnet(null); + if (!chainId) return; + + setIsLoading(true); + getBlockchainInfo(chainId) + .then(async (chainInfo) => { + setSubnetId(chainInfo.subnetId); + try { + const subnetInfo = await getSubnetInfo(chainInfo.subnetId); + setSubnet(subnetInfo); + } catch (error) { + setSubnetIdError((error as Error).message); + } + }) + .catch((error) => { + setSubnetIdError((error as Error).message); + }) + .finally(() => { + setIsLoading(false); + }); + }, [chainId]); + useEffect(() => { try { setRpcCommand(generateDockerCommand([subnetId], isRPC, avalancheNetworkID, enableDebugTrace ? chainId : undefined)); @@ -195,22 +224,10 @@ export default function AvalanchegoDocker() { } }, [isRPC]); - - useEffect(() => { - setSubnetIdError(null); - setSubnetId(""); - if (!chainId) return - - getBlockchainInfo(chainId).then((chainInfo) => { - setSubnetId(chainInfo.subnetId); - }).catch((error) => { - setSubnetIdError((error as Error).message); - }); - }, [chainId]); - const handleReset = () => { setChainId(""); setSubnetId(""); + setSubnet(null); setIsRPC(true); setChainAddedToWallet(null); setRpcCommand(""); @@ -288,14 +305,17 @@ export default function AvalanchegoDocker() { - - + + {/* Show subnet details if available */} + diff --git a/toolbox/src/toolbox/Nodes/PerformanceMonitor.tsx b/toolbox/src/toolbox/Nodes/PerformanceMonitor.tsx index 0056573ec9f..f2c10baf465 100644 --- a/toolbox/src/toolbox/Nodes/PerformanceMonitor.tsx +++ b/toolbox/src/toolbox/Nodes/PerformanceMonitor.tsx @@ -9,6 +9,7 @@ import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, Responsi import { BlockWatcher, BlockInfo } from "./BlockWatcher"; import { ChainInfo } from "./chainInfo"; import { RPCURLInput } from "../../components/RPCURLInput"; +import InputSubnetId from "../../components/InputSubnetId"; interface BucketedData { transactions: number; @@ -19,7 +20,7 @@ interface BucketedData { export default function PerformanceMonitor() { const [nodeRpcUrl, setNodeRpcUrl] = useState(''); const [chainID, setChainID] = useState(''); - const [subnetId, setSubnetID] = useState(''); + const [subnetId, setSubnetId] = useState(''); const [evmChainRpcUrl, setEvmChainRpcUrl] = useState(''); @@ -260,7 +261,6 @@ export default function PerformanceMonitor() { label="RPC URL excluding /ext/bc/..." value={nodeRpcUrl} onChange={setNodeRpcUrl} - disabled={isMonitoring} /> -