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/DetailsDisplay.tsx b/toolbox/src/components/DetailsDisplay.tsx
new file mode 100644
index 00000000000..3d5095ddd8a
--- /dev/null
+++ b/toolbox/src/components/DetailsDisplay.tsx
@@ -0,0 +1,283 @@
+"use client"
+
+import { Calendar, Users, Database, Key, Copy, AlertTriangle, FileText, Globe } from "lucide-react"
+import { useState } from "react"
+import { Subnet } from "@avalabs/avacloud-sdk/models/components";
+import type { BlockchainInfo } from "./SelectBlockchain";
+
+interface DetailsDisplayProps {
+ type: 'blockchain' | 'subnet'
+ data: BlockchainInfo | Subnet | null
+ isLoading?: boolean
+}
+
+export default function DetailsDisplay({ type, data, isLoading }: DetailsDisplayProps) {
+ const [copiedText, setCopiedText] = useState(null)
+
+ // Standard EVM VM ID
+ const STANDARD_EVM_VM_ID = "srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy"
+
+ if (isLoading) {
+ return (
+
+
+
+
+
+ Loading {type} details...
+
+
+
+
+ )
+ }
+
+ if (!data) {
+ return null
+ }
+
+ const formatTimestamp = (timestamp: number) => {
+ const date = new Date(timestamp * 1000)
+ return date.toLocaleDateString("en-US", {
+ year: "2-digit",
+ month: "short",
+ day: "numeric",
+ })
+ }
+
+ const copyToClipboard = (text: string | number | undefined | null) => {
+ if (!text) return
+ navigator.clipboard.writeText(text.toString())
+ setCopiedText(text.toString())
+ setTimeout(() => setCopiedText(null), 2000)
+ }
+
+ // Determine if we're showing subnet or blockchain
+ const isSubnet = type === 'subnet'
+ const subnet = isSubnet ? (data as Subnet) : null
+
+ // Get blockchain data - either directly or from subnet's first blockchain
+ const blockchain = isSubnet
+ ? (subnet?.blockchains?.[0] ? { ...(subnet.blockchains[0] as any), isTestnet: false } : null)
+ : (data as BlockchainInfo)
+
+ return (
+
+ {/* Header */}
+
+
+
+ {isSubnet ? 'Subnet Details' : 'Blockchain Details'}
+
+
+ {isSubnet && subnet?.isL1 && (
+
+ L1 Chain
+
+ )}
+ {blockchain?.isTestnet !== undefined && (
+
+ {blockchain.isTestnet ? "Testnet" : "Mainnet"}
+
+ )}
+
+
+
+
+ {/* Content Area */}
+
+ {/* Subnet-specific information */}
+ {isSubnet && subnet && (
+ <>
+ {/* Basic Subnet Information */}
+
+
+
+
+ Created:
+ {formatTimestamp(subnet.createBlockTimestamp)}
+
+
+
+ Chains:
+ {subnet.blockchains?.length || 0}
+
+
+
+ {subnet.subnetOwnershipInfo.addresses && subnet.subnetOwnershipInfo.addresses.length > 0 && (
+
+
+
+
Owner:
+
+
+ {subnet.subnetOwnershipInfo.addresses[0]}
+
+
+
+
+
+ {subnet.subnetOwnershipInfo.threshold} of {subnet.subnetOwnershipInfo.addresses.length} signatures required
+
+
+ )}
+
+
+
+ {/* L1 Conversion Information */}
+ {subnet.isL1 && subnet.l1ValidatorManagerDetails && (
+
+
+
+ L1 Conversion
+
+
+
+
Validator Manager:
+
+
+ {subnet.l1ValidatorManagerDetails?.contractAddress}
+
+
+
+
+
+
+ )}
+ >
+ )}
+
+ {/* Blockchain Information */}
+ {blockchain && (
+
+
+
+
+ {isSubnet ? 'Blockchain Details' : 'Details'}
+
+
+
+ {/* Basic blockchain info for blockchain-only view */}
+ {!isSubnet && (
+
+
+
+
+ Created:
+ {formatTimestamp(blockchain.createBlockTimestamp)}
+
+
+
+ Name:
+ {blockchain.blockchainName || "Unknown"}
+
+
+
+
+
+ Create Block:
+ {blockchain.createBlockNumber}
+
+
+
+ )}
+
+
+
+
+
EVM Chain ID:
+
+
+ {blockchain.evmChainId}
+
+
+
+
+
+
+
Blockchain ID:
+
+
+ {blockchain.blockchainId}
+
+
+
+
+
+
+
Subnet ID:
+
+
+ {blockchain.subnetId}
+
+
+
+
+
+
+
VM ID:
+
+
+ {blockchain.vmId}
+
+
+
+
+
+
+ {/* Warning for non-standard VM */}
+ {blockchain.vmId && blockchain.vmId !== STANDARD_EVM_VM_ID && (
+
+
+
Non-standard VM detected
+
+ )}
+
+
+ )}
+
+ {/* Copy feedback */}
+ {copiedText && (
+
+ Copied to clipboard!
+
+ )}
+
+
+ )
+}
\ 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/SelectBlockchain.tsx b/toolbox/src/components/SelectBlockchain.tsx
new file mode 100644
index 00000000000..e548c732516
--- /dev/null
+++ b/toolbox/src/components/SelectBlockchain.tsx
@@ -0,0 +1,121 @@
+"use client"
+
+import SelectBlockchainId from "./SelectBlockchainId";
+import { useState, useCallback } from "react";
+import { useWalletStore } from "../stores/walletStore";
+import { networkIDs } from "@avalabs/avalanchejs";
+
+// API Response type from AvaCloud - matches the official API response
+export type BlockchainApiResponse = {
+ createBlockTimestamp: number;
+ createBlockNumber: string;
+ blockchainId: string;
+ vmId: string;
+ subnetId: string;
+ blockchainName: string;
+ evmChainId: number;
+}
+
+// Extended type with additional metadata
+export type BlockchainInfo = BlockchainApiResponse & {
+ isTestnet: boolean;
+}
+
+export type BlockchainSelection = {
+ blockchainId: string;
+ blockchain: BlockchainInfo | null;
+}
+
+// Import the unified details display component
+import DetailsDisplay from "./DetailsDisplay";
+
+export default function SelectBlockchain({
+ value,
+ onChange,
+ error,
+ label = "Select Avalanche Blockchain ID"
+}: {
+ value: string,
+ onChange: (selection: BlockchainSelection) => void,
+ error?: string | null,
+ label?: string
+}) {
+ const { avalancheNetworkID } = useWalletStore();
+ const [blockchainDetails, setBlockchainDetails] = useState>({});
+ const [isLoading, setIsLoading] = useState(false);
+
+ // Network names for API calls
+ const networkNames: Record = {
+ [networkIDs.MainnetID]: "mainnet",
+ [networkIDs.FujiID]: "fuji",
+ };
+
+ // Fetch blockchain details when needed
+ const fetchBlockchainDetails = useCallback(async (blockchainId: string) => {
+ if (!blockchainId || blockchainDetails[blockchainId]) return;
+
+ try {
+ const network = networkNames[Number(avalancheNetworkID)];
+ if (!network) return;
+
+ setIsLoading(true);
+
+ // Use direct API call as shown in AvaCloud documentation
+ // https://developers.avacloud.io/data-api/primary-network/get-blockchain-details-by-id
+ const response = await fetch(`https://glacier-api.avax.network/v1/networks/${network}/blockchains/${blockchainId}`, {
+ method: 'GET',
+ headers: {
+ 'accept': 'application/json',
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error(`Failed to fetch blockchain details: ${response.statusText}`);
+ }
+
+ const blockchain: BlockchainApiResponse = await response.json();
+
+ setBlockchainDetails(prev => ({
+ ...prev,
+ [blockchainId]: {
+ ...blockchain,
+ isTestnet: network === "fuji"
+ }
+ }));
+ } catch (error) {
+ console.error(`Error fetching blockchain details for ${blockchainId}:`, error);
+ } finally {
+ setIsLoading(false);
+ }
+ }, [avalancheNetworkID, networkNames, blockchainDetails]);
+
+ // Handle value change and fetch details if needed
+ const handleValueChange = useCallback((newValue: string) => {
+ if (newValue && !blockchainDetails[newValue]) {
+ fetchBlockchainDetails(newValue);
+ }
+
+ onChange({
+ blockchainId: newValue,
+ blockchain: blockchainDetails[newValue] || null
+ });
+ }, [fetchBlockchainDetails, blockchainDetails, onChange]);
+
+ // Get current blockchain details for display
+ const currentBlockchain = value ? blockchainDetails[value] || null : null;
+ const isLoadingCurrent = value && !blockchainDetails[value] && isLoading;
+
+ return (
+
+
+
+ {/* Display blockchain details when a blockchain is selected */}
+ {value && }
+
+ );
+}
\ No newline at end of file
diff --git a/toolbox/src/components/SelectChainID.tsx b/toolbox/src/components/SelectBlockchainId.tsx
similarity index 96%
rename from toolbox/src/components/SelectChainID.tsx
rename to toolbox/src/components/SelectBlockchainId.tsx
index 33e49960fee..2535a2a762d 100644
--- a/toolbox/src/components/SelectChainID.tsx
+++ b/toolbox/src/components/SelectBlockchainId.tsx
@@ -5,14 +5,14 @@ import { useMemo } from "react";
import { cn } from "../lib/utils";
import { Globe } from 'lucide-react';
-interface ChainOption {
+interface BlockchainOption {
id: string;
name: string;
description: string;
logoUrl?: string;
}
-export default function SelectChainID({
+export default function SelectBlockchainId({
value,
onChange,
error,
@@ -28,8 +28,8 @@ export default function SelectChainID({
const { l1List } = useL1ListStore()();
const selectId = useId();
- const options: ChainOption[] = useMemo(() => {
- const result: ChainOption[] = [];
+ const options: BlockchainOption[] = useMemo(() => {
+ const result: BlockchainOption[] = [];
if (createChainStorechainID) {
result.push({
@@ -90,7 +90,7 @@ export default function SelectChainID({
) : (
- Select a chain ID
+ Select a blockchain ID
)}
@@ -129,4 +129,4 @@ export default function SelectChainID({
{error && {error}
}
);
-}
+}
\ 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..6fc770401e3
--- /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 DetailsDisplay from "./DetailsDisplay";
+
+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/toolbox/ICM/SendICMMessage.tsx b/toolbox/src/toolbox/ICM/SendICMMessage.tsx
index cd0223f3e42..1858d64e422 100644
--- a/toolbox/src/toolbox/ICM/SendICMMessage.tsx
+++ b/toolbox/src/toolbox/ICM/SendICMMessage.tsx
@@ -11,7 +11,7 @@ import ICMDemoABI from "../../../contracts/example-contracts/compiled/ICMDemo.js
import { utils } from "@avalabs/avalanchejs";
import { Input } from "../../components/Input";
import { Container } from "../../components/Container";
-import SelectChainID from "../../components/SelectChainID";
+import SelectBlockchainId from "../../components/SelectBlockchainId";
import { useL1ByChainId, useSelectedL1 } from "../../stores/l1ListStore";
import { useEffect } from "react";
const predeployedDemos: Record = {
@@ -174,7 +174,7 @@ export default function SendICMMessage() {
required
type="number"
/>
- setDestinationChainId(value)}
diff --git a/toolbox/src/toolbox/ICTT/AddCollateral.tsx b/toolbox/src/toolbox/ICTT/AddCollateral.tsx
index fe44a83dd9b..8e436ff886e 100644
--- a/toolbox/src/toolbox/ICTT/AddCollateral.tsx
+++ b/toolbox/src/toolbox/ICTT/AddCollateral.tsx
@@ -12,7 +12,7 @@ import { Input, Suggestion } from "../../components/Input";
import { EVMAddressInput } from "../../components/EVMAddressInput";
import { utils } from "@avalabs/avalanchejs";
import { Note } from "../../components/Note";
-import SelectChainID from "../../components/SelectChainID";
+import SelectBlockchainId from "../../components/SelectBlockchainId";
import { Container } from "../../components/Container";
import ERC20TokenRemoteABI from "../../../contracts/icm-contracts/compiled/ERC20TokenRemote.json";
import { getToolboxStore, useViemChainStore } from "../../stores/toolboxStore";
@@ -324,7 +324,7 @@ export default function AddCollateral() {
description="Approve and add collateral (ERC20 tokens) to the Token Home contract on the source chain for a remote bridge contract on the current chain."
>
- setSourceChainId(value)}
diff --git a/toolbox/src/toolbox/ICTT/DeployERC20TokenRemote.tsx b/toolbox/src/toolbox/ICTT/DeployERC20TokenRemote.tsx
index 178662ae00b..f98bd2c6535 100644
--- a/toolbox/src/toolbox/ICTT/DeployERC20TokenRemote.tsx
+++ b/toolbox/src/toolbox/ICTT/DeployERC20TokenRemote.tsx
@@ -15,7 +15,7 @@ import { Note } from "../../components/Note";
import { utils } from "@avalabs/avalanchejs";
import ERC20TokenHomeABI from "../../../contracts/icm-contracts/compiled/ERC20TokenHome.json";
import ExampleERC20 from "../../../contracts/icm-contracts/compiled/ExampleERC20.json";
-import SelectChainID from "../../components/SelectChainID";
+import SelectBlockchainId from "../../components/SelectBlockchainId";
import { Container } from "../../components/Container";
import TeleporterRegistryAddressInput from "../../components/TeleporterRegistryAddressInput";
@@ -220,7 +220,7 @@ export default function DeployERC20TokenRemote() {
}
- setSourceChainId(value)}
diff --git a/toolbox/src/toolbox/ICTT/DeployNativeTokenRemote.tsx b/toolbox/src/toolbox/ICTT/DeployNativeTokenRemote.tsx
index c83cf6c60a2..90af6dac77a 100644
--- a/toolbox/src/toolbox/ICTT/DeployNativeTokenRemote.tsx
+++ b/toolbox/src/toolbox/ICTT/DeployNativeTokenRemote.tsx
@@ -15,7 +15,7 @@ import { Note } from "../../components/Note";
import { utils } from "@avalabs/avalanchejs";
import ERC20TokenHomeABI from "../../../contracts/icm-contracts/compiled/ERC20TokenHome.json";
import ExampleERC20 from "../../../contracts/icm-contracts/compiled/ExampleERC20.json";
-import SelectChainID from "../../components/SelectChainID";
+import SelectBlockchainId from "../../components/SelectBlockchainId";
import { CheckPrecompile } from "../../components/CheckPrecompile";
import { Container } from "../../components/Container";
import TeleporterRegistryAddressInput from "../../components/TeleporterRegistryAddressInput";
@@ -207,7 +207,7 @@ export default function DeployNativeTokenRemote() {
}
- setSourceChainId(value)}
diff --git a/toolbox/src/toolbox/ICTT/RegisterWithHome.tsx b/toolbox/src/toolbox/ICTT/RegisterWithHome.tsx
index f0913e84dd1..ee798017edf 100644
--- a/toolbox/src/toolbox/ICTT/RegisterWithHome.tsx
+++ b/toolbox/src/toolbox/ICTT/RegisterWithHome.tsx
@@ -14,7 +14,7 @@ import { Suggestion } from "../../components/Input";
import { EVMAddressInput } from "../../components/EVMAddressInput";
import { utils } from "@avalabs/avalanchejs";
import { ListContractEvents } from "../../components/ListContractEvents";
-import SelectChainID from "../../components/SelectChainID";
+import SelectBlockchainId from "../../components/SelectBlockchainId";
import { Container } from "../../components/Container";
export default function RegisterWithHome() {
@@ -179,7 +179,7 @@ export default function RegisterWithHome() {
- setSourceChainId(value)}
diff --git a/toolbox/src/toolbox/ICTT/TestSend.tsx b/toolbox/src/toolbox/ICTT/TestSend.tsx
index 96cc36a7071..a07499a3783 100644
--- a/toolbox/src/toolbox/ICTT/TestSend.tsx
+++ b/toolbox/src/toolbox/ICTT/TestSend.tsx
@@ -18,7 +18,7 @@ import { Suggestion } from "../../components/TokenInput";
import { EVMAddressInput } from "../../components/EVMAddressInput";
import { Token, TokenInput } from "../../components/TokenInputToolbox";
import { utils } from "@avalabs/avalanchejs";
-import SelectChainID from "../../components/SelectChainID";
+import SelectBlockchain, { type BlockchainSelection } from "../../components/SelectBlockchain";
import { Container } from "../../components/Container";
import { Toggle } from "../../components/Toggle";
import { Ellipsis } from "lucide-react";
@@ -32,7 +32,7 @@ export default function TokenBridge() {
const selectedL1 = useSelectedL1()();
// Only need to select destination chain (source is current chain)
- const [destinationChainId, setDestinationChainId] = useState("");
+ const [destinationSelection, setDestinationSelection] = useState({ blockchainId: "", blockchain: null });
// Contract addresses
const [sourceContractAddress, setSourceContractAddress] = useState("");
@@ -72,17 +72,17 @@ export default function TokenBridge() {
const [tokenAllowance, setTokenAllowance] = useState(null);
// Get chain info - source is current chain, destination is selected
- const destL1 = useL1ByChainId(destinationChainId)();
- const destToolboxStore = getToolboxStore(destinationChainId)();
+ const destL1 = useL1ByChainId(destinationSelection.blockchainId)();
+ const destToolboxStore = getToolboxStore(destinationSelection.blockchainId)();
const { erc20TokenHomeAddress, nativeTokenHomeAddress } = useToolboxStore();
// Destination chain validation
let destChainError: string | undefined = undefined;
- if (!destinationChainId) {
- destChainError = "Please select a destination chain";
- } else if (destinationChainId === selectedL1?.id) {
- destChainError = "Source and destination chains must be different";
+ if (!destinationSelection.blockchainId) {
+ destChainError = "Please select a destination blockchain";
+ } else if (destinationSelection.blockchainId === selectedL1?.id) {
+ destChainError = "Source and destination blockchains must be different";
}
// Generate hex blockchain ID for the destination chain
@@ -91,7 +91,7 @@ export default function TokenBridge() {
try {
return utils.bufferToHex(utils.base58check.decode(destL1.id));
} catch (e) {
- console.error("Error decoding destination chain ID:", e);
+ console.error("Error decoding destination blockchain ID:", e);
return null;
}
}, [destL1?.id]);
@@ -478,10 +478,10 @@ export default function TokenBridge() {
description={`Send tokens from the current chain (${selectedL1?.name}) to another chain.`}
>
- setDestinationChainId(value)}
+
@@ -497,14 +497,14 @@ export default function TokenBridge() {
/>
setDestinationContractAddress(value as Address)}
verify={(value) => fetchTokenInfoFromBridgeContract(value as Address, "destination")}
- disabled={!destinationChainId || isProcessingSend || isProcessingApproval}
+ disabled={!destinationSelection.blockchainId || isProcessingSend || isProcessingApproval}
suggestions={destinationContractSuggestions}
- placeholder="0x... Bridge contract on destination chain"
+ placeholder="0x... Bridge contract on destination blockchain"
/>
= {
+ [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."
>
-
@@ -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 446ed623161..d9aeebfbca7 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"
@@ -49,6 +50,10 @@ export default function CreateChain() {
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);
@@ -111,7 +116,7 @@ export default function CreateChain() {
disabled={true}
type="text"
/>
-
+
-
- {createdSubnetId && (
+
+ {createdSubnetId && (
+
- )}
-
+
+ )}
Step 2: Create a Chain
@@ -134,12 +140,11 @@ export default function CreateChain() {
Enter the parameters for your new chain.
-
(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 && (