Skip to content

SelectSubnet and input checking existence of subnet #2531

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/tools/common/api/validator-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
});
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -128,4 +128,4 @@
"typescript": "^5.8.2"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
}
4 changes: 2 additions & 2 deletions toolbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -72,4 +72,4 @@
"vite": "^6.1.6",
"vite-plugin-dts": "^4.5.0"
}
}
}
142 changes: 142 additions & 0 deletions toolbox/src/components/InputSubnetId.tsx
Original file line number Diff line number Diff line change
@@ -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<string | null>(null);

// Network names for API calls
const networkNames: Record<number, GlobalParamNetwork> = {
[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<string>();

// 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 (
<Input
id={id}
label={label}
value={value}
onChange={onChange}
suggestions={subnetIdSuggestions}
error={combinedError}
helperText={helperText}
placeholder="Enter subnet ID"
/>
);
}
102 changes: 102 additions & 0 deletions toolbox/src/components/SelectSubnet.tsx
Original file line number Diff line number Diff line change
@@ -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<Record<string, Subnet>>({});
const [isLoading, setIsLoading] = useState(false);

// Network names for API calls
const networkNames: Record<number, GlobalParamNetwork> = {
[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 (
<div>
<SelectSubnetId
id="subnet-input"
label="Subnet"
value={value}
onChange={handleValueChange}
error={error}
onlyNotConverted={onlyNotConverted}
hidePrimaryNetwork={hidePrimaryNetwork}
helperText={isLoading ? "Loading subnet details..." : undefined}
/>

{/* Display subnet details when a subnet is selected */}
{value && <SubnetDetailsDisplay subnet={currentSubnet} isLoading={!!isLoadingCurrent} />}
</div>
);
}
Loading