diff --git a/toolbox/src/coreViem/methods/extractWarpMessageFromPChainTx.ts b/toolbox/src/coreViem/methods/extractWarpMessageFromPChainTx.ts index 7b3cc62df67..2b03ba2c7d5 100644 --- a/toolbox/src/coreViem/methods/extractWarpMessageFromPChainTx.ts +++ b/toolbox/src/coreViem/methods/extractWarpMessageFromPChainTx.ts @@ -94,6 +94,7 @@ export type ExtractWarpMessageFromTxParams = { export type ExtractWarpMessageFromTxResponse = { message: string; justification: string; + subnetId: string; signingSubnetId: string; networkId: typeof networkIDs.FujiID | typeof networkIDs.MainnetID; validators: Validator[]; @@ -101,6 +102,40 @@ export type ExtractWarpMessageFromTxResponse = { managerAddress: string; } +// FIXME: This should be included in avacloud-sdk but I'm afraid to version bump right now +// if you have better idea to get the subnetId from a blockchainId, please go ahead and change it +/** + * Fetches blockchain information from Glacier API + * @param network "fuji" or "mainnet" + * @param blockchainId The blockchain ID to query + * @returns The subnet ID associated with the blockchain + */ +export async function getSubnetIdFromChainId(network: "fuji" | "mainnet", blockchainId: string): Promise { + try { + 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(`Glacier API returned ${response.status}: ${response.statusText}`); + } + + const data = await response.json(); + + if (!data.subnetId) { + throw new Error('No subnetId found in response'); + } + + return data.subnetId; + } catch (error) { + console.error('Error fetching subnet info from Glacier:', error); + throw error; + } +} + //TODO: rename export async function extractWarpMessageFromPChainTx(client: WalletClient, { txId }: ExtractWarpMessageFromTxParams): Promise { const isTestnetMode = await isTestnet(client); @@ -146,11 +181,13 @@ export async function extractWarpMessageFromPChainTx(client: WalletClient state.subnetId) // --- Form Input State --- const [nodeID, setNodeID] = useState("") const [weight, setWeight] = useState("") const [manualProxyAddress, setManualProxyAddress] = useState(selectedL1?.validatorManagerAddress || "") - const [manualSubnetId, setManualSubnetId] = useState(selectedL1?.subnetId || "") + const [currentSubnetId, setCurrentSubnetId] = useState(createChainStoreSubnetId || selectedL1?.subnetId || "") // --- Intermediate Data State --- const [validationIDHex, setValidationIDHex] = useState("") @@ -226,11 +228,15 @@ export default function ChangeWeight() { throw new Error("Warp message is empty. Retry step 2.") } + if (!selectedL1?.subnetId) { + throw new Error("No subnet ID available. Please select a valid L1 or specify a subnet ID.") + } + const { signedMessage: signedMessageResult } = await new AvaCloudSDK().data.signatureAggregator.aggregateSignatures({ network: networkName, signatureAggregatorRequest: { - message: warpMessageToSign, // Use potentially updated local message - signingSubnetId: manualSubnetId || "", + message: warpMessageToSign, + signingSubnetId: selectedL1.subnetId, quorumPercentage: 67, }, }) @@ -285,14 +291,14 @@ export default function ChangeWeight() { const eventDataForPacking = localEventData || eventData; if (!viemChain) throw new Error("Viem chain configuration is missing.") - if (!validationIDForJustification) throw new Error("Validation ID is missing. Retry step 1.") - if (!manualSubnetId) throw new Error("Subnet ID is missing.") + if (!validationIDForJustification) throw new Error("Validation ID is missing.") + if (!currentSubnetId) throw new Error("Subnet ID is missing.") if (!eventDataForPacking) throw new Error("Event data missing. Retry step 2.") const justification = await GetRegistrationJustification( nodeID, validationIDForJustification, - manualSubnetId, + currentSubnetId, publicClient ) @@ -316,12 +322,16 @@ export default function ChangeWeight() { console.log("Change Weight Message Hex:", bytesToHex(changeWeightMessage)) console.log("Justification:", justification) + if (!selectedL1?.subnetId) { + throw new Error("No subnet ID available. Please select a valid L1 or specify a subnet ID.") + } + const signature = await new AvaCloudSDK().data.signatureAggregator.aggregateSignatures({ network: networkName, signatureAggregatorRequest: { message: bytesToHex(changeWeightMessage), justification: bytesToHex(justification), - signingSubnetId: manualSubnetId || "", + signingSubnetId: selectedL1.subnetId, quorumPercentage: 67, }, }) @@ -357,9 +367,8 @@ export default function ChangeWeight() { if (!publicClient) throw new Error("Public client is not initialized.") if (!viemChain) throw new Error("Viem chain is not configured.") - let simulationResult - try { - simulationResult = await publicClient.simulateContract({ + + const hash = await coreWalletClient.writeContract({ address: manualProxyAddress as `0x${string}`, abi: validatorManagerAbi.abi, functionName: "completeValidatorWeightUpdate", @@ -368,30 +377,11 @@ export default function ChangeWeight() { account: coreWalletClient.account, chain: viemChain }) - console.log("Simulation successful:", simulationResult) - } catch (simError: any) { - console.error("Contract simulation failed:", simError) - const baseError = simError.cause || simError - const reason = baseError?.shortMessage || simError.message || "Simulation failed, reason unknown." - throw new Error(`Contract simulation failed: ${reason}`) - } - - console.log("Simulation request:", simulationResult.request) - - let txHash - try { - txHash = await coreWalletClient.writeContract(simulationResult.request) - console.log("Transaction sent:", txHash) - } catch (writeError: any) { - console.error("Contract write failed:", writeError) - const baseError = writeError.cause || writeError - const reason = baseError?.shortMessage || writeError.message || "Transaction submission failed, reason unknown." - throw new Error(`Submitting transaction failed: ${reason}`) - } + console.log("Transaction sent:", hash) let receipt try { - receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }) + receipt = await publicClient.waitForTransactionReceipt({ hash }) console.log("Transaction receipt:", receipt) if (receipt.status !== 'success') { throw new Error(`Transaction failed with status: ${receipt.status}`) @@ -507,25 +497,13 @@ export default function ChangeWeight() {
-

- Override the current subnet ID (or leave empty to use default) + Select a subnet ID (defaults to subnet from Create Subnet tool if available)

diff --git a/toolbox/src/toolbox/ValidatorManager/InitValidatorSet.tsx b/toolbox/src/toolbox/ValidatorManager/InitValidatorSet.tsx index 49625fd1eac..f1ea84c6e37 100644 --- a/toolbox/src/toolbox/ValidatorManager/InitValidatorSet.tsx +++ b/toolbox/src/toolbox/ValidatorManager/InitValidatorSet.tsx @@ -25,7 +25,7 @@ export default function InitValidatorSet() { const { coreWalletClient, publicClient } = useWalletStore(); const [isInitializing, setIsInitializing] = useState(false); const [txHash, setTxHash] = useState(null); - const [simulationWentThrough, setSimulationWentThrough] = useState(false); + const [simulationWentThrough, _] = useState(false); const [error, setError] = useState(null); const [collectedData, setCollectedData] = useState>({}); const [showDebugData, setShowDebugData] = useState(false); @@ -90,12 +90,11 @@ export default function InitValidatorSet() { try { if (!coreWalletClient) throw new Error('Core wallet client not found'); - const { validators, signingSubnetId, chainId, managerAddress } = await coreWalletClient.extractWarpMessageFromPChainTx({ txId: conversionTxID }); - + const { validators, subnetId, chainId, managerAddress } = await coreWalletClient.extractWarpMessageFromPChainTx({ txId: conversionTxID }); // Prepare transaction arguments const txArgs = [ { - subnetID: cb58ToHex(signingSubnetId), + subnetID: cb58ToHex(subnetId), validatorManagerBlockchainID: cb58ToHex(chainId), validatorManagerAddress: managerAddress as `0x${string}`, initialValidators: validators @@ -128,7 +127,8 @@ export default function InitValidatorSet() { const signatureBytes = hexToBytes(add0x(L1ConversionSignature)); const accessList = packWarpIntoAccessList(signatureBytes); - const sim = await publicClient.simulateContract({ + // FIXME: for whatever reason, viem simulation does not work consistently, so we just send the transaction + const hash = await coreWalletClient.writeContract({ address: managerAddress as `0x${string}`, abi: ValidatorManagerABI.abi, functionName: 'initializeValidatorSet', @@ -138,14 +138,14 @@ export default function InitValidatorSet() { chain: viemChain || undefined, }); - console.log("Simulated transaction:", sim); - setSimulationWentThrough(true); + // console.log("Simulated transaction:", sim); + // setSimulationWentThrough(true); - console.log("sim", JSON.stringify(sim, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); + // console.log("sim", JSON.stringify(sim, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2)); // Send transaction - const hash = await coreWalletClient.writeContract(sim.request); + // const hash = await coreWalletClient.writeContract(sim.request); // Wait for transaction confirmation const receipt = await publicClient.waitForTransactionReceipt({ hash }); diff --git a/toolbox/src/toolbox/ValidatorManager/Initialize.tsx b/toolbox/src/toolbox/ValidatorManager/Initialize.tsx index 478e1e5a1a2..e2234bb93b6 100644 --- a/toolbox/src/toolbox/ValidatorManager/Initialize.tsx +++ b/toolbox/src/toolbox/ValidatorManager/Initialize.tsx @@ -1,6 +1,6 @@ "use client"; -import { useSelectedL1, useViemChainStore } from "../toolboxStore"; +import { useSelectedL1, useViemChainStore, useCreateChainStore } from "../toolboxStore"; import { useWalletStore } from "../../lib/walletStore"; import { useErrorBoundary } from "react-error-boundary"; import { useEffect, useState } from "react"; @@ -10,6 +10,7 @@ import { ResultField } from "../components/ResultField"; import { AbiEvent } from 'viem'; import ValidatorManagerABI from "../../../contracts/icm-contracts/compiled/ValidatorManager.json"; import { utils } from "@avalabs/avalanchejs"; +import SelectSubnetId from "../components/SelectSubnetId"; import { Container } from "../components/Container"; import { getSubnetInfo } from "../../coreViem/utils/glacier"; @@ -26,6 +27,8 @@ export default function Initialize() { const [adminAddress, setAdminAddress] = useState(""); const viemChain = useViemChainStore(); const selectedL1 = useSelectedL1()(); + const [subnetId, setSubnetId] = useState(""); + const createChainStoreSubnetId = useCreateChainStore()(state => state.subnetId); useEffect(() => { if (walletEVMAddress && !adminAddress) { @@ -33,9 +36,17 @@ export default function Initialize() { } }, [walletEVMAddress, adminAddress]); + useEffect(() => { + if (createChainStoreSubnetId && !subnetId) { + setSubnetId(createChainStoreSubnetId); + } else if (selectedL1?.subnetId && !subnetId) { + setSubnetId(selectedL1.subnetId); + } + }, [createChainStoreSubnetId, selectedL1, subnetId]); + let subnetIDHex = ""; try { - subnetIDHex = utils.bufferToHex(utils.base58check.decode(selectedL1?.subnetId || "")); + subnetIDHex = utils.bufferToHex(utils.base58check.decode(subnetId || "")); } catch (error) { console.error('Error decoding subnetId:', error); } @@ -51,7 +62,6 @@ export default function Initialize() { useEffect(() => { setContractAddressError(""); - const subnetId = selectedL1?.subnetId; if (!subnetId) return; getSubnetInfo(subnetId).then((subnetInfo) => { setProxyAddress(subnetInfo.l1ValidatorManagerDetails?.contractAddress || ""); @@ -59,7 +69,7 @@ export default function Initialize() { console.error('Error getting subnet info:', error); setContractAddressError((error as Error)?.message || "Unknown error"); }); - }, [selectedL1]); + }, [subnetId]); @@ -76,10 +86,38 @@ export default function Initialize() { throw new Error('Initialized event not found in ABI'); } + // Instead of querying from block 0, try to check initialization status using the contract method first + try { + // Try to call a read-only method that would fail if not initialized + const isInit = await publicClient.readContract({ + address: proxyAddress as `0x${string}`, + abi: ValidatorManagerABI.abi, + functionName: 'admin' + }); + + // If we get here without error, contract is initialized + setIsInitialized(true); + console.log('Contract is initialized, admin:', isInit); + return; + } catch (readError) { + // If this fails with a specific revert message about not being initialized, we know it's not initialized + if ((readError as any)?.message?.includes('not initialized')) { + setIsInitialized(false); + return; + } + // Otherwise, fallback to log checking with a smaller block range + } + + // Fallback: Check logs but with a more limited range + // Get current block number + const latestBlock = await publicClient.getBlockNumber(); + // Use a reasonable range (2000 blocks) or start from recent blocks + const fromBlock = latestBlock > 2000n ? latestBlock - 2000n : 0n; + const logs = await publicClient.getLogs({ address: proxyAddress as `0x${string}`, event: initializedEvent as AbiEvent, - fromBlock: 0n, + fromBlock: fromBlock, toBlock: 'latest' }); @@ -159,10 +197,9 @@ export default function Initialize() { - ([]) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState(null) const [selectedValidator, setSelectedValidator] = useState(null) const [copiedNodeId, setCopiedNodeId] = useState(null) + const [subnetId, setSubnetId] = useState("") // Network names for display const networkNames: Record = { @@ -25,10 +25,6 @@ export default function QueryL1ValidatorSet() { [networkIDs.FujiID]: "fuji", } - useEffect(() => { - fetchValidators() - }, [avalancheNetworkID, selectedL1?.subnetId]) - async function fetchValidators() { setIsLoading(true) setError(null) @@ -42,7 +38,7 @@ export default function QueryL1ValidatorSet() { const result = await new AvaCloudSDK().data.primaryNetwork.listL1Validators({ network: network, - subnetId: selectedL1?.subnetId || "", + subnetId: subnetId || "", }); // Handle pagination @@ -106,6 +102,9 @@ export default function QueryL1ValidatorSet() {
+
+ +
-

- Override the current subnet ID (or leave empty to use default) + Optional: Subnet ID for justification retrieval (defaults to selected L1 subnet ID)

diff --git a/toolbox/src/toolbox/ValidatorManager/justification.tsx b/toolbox/src/toolbox/ValidatorManager/justification.tsx index b3335633049..34ad6a0abfd 100644 --- a/toolbox/src/toolbox/ValidatorManager/justification.tsx +++ b/toolbox/src/toolbox/ValidatorManager/justification.tsx @@ -1,4 +1,4 @@ -import { parseAbiItem, type PublicClient, hexToBytes } from 'viem'; +import { parseAbiItem, hexToBytes } from 'viem'; import { unpackRegisterL1ValidatorPayload, calculateValidationID, SolidityValidationPeriod } from '../../coreViem/utils/convertWarp'; import { utils } from '@avalabs/avalanchejs'; import { Buffer } from 'buffer'; // Import Buffer @@ -305,7 +305,7 @@ export async function GetRegistrationJustification( nodeID: string, // Keep for logging/confirmation validationIDHex: string, subnetIDStr: string, - publicClient: { getLogs: PublicClient['getLogs'] } + publicClient: { getBlockNumber: () => Promise, getLogs: (args: any) => Promise } ): Promise { const WARP_ADDRESS = '0x0200000000000000000000000000000000000005' as const; const NUM_BOOTSTRAP_VALIDATORS_TO_SEARCH = 100; @@ -353,77 +353,115 @@ export async function GetRegistrationJustification( // 2. If not a bootstrap validator, search Warp logs try { - const warpLogs = await publicClient.getLogs({ - address: WARP_ADDRESS, - event: sendWarpMessageEventAbi, - fromBlock: 'earliest', - toBlock: 'latest', - }); - - if (warpLogs.length === 0) { - console.log(`No Warp logs found.`); - return null; - } - - console.log(`Found ${warpLogs.length} Warp logs. Searching for justification matching ValidationID ${validationIDHex}...`); - - for (const log of warpLogs) { - try { - const decodedArgs = log.args as { message?: `0x${string}` }; - const fullMessageHex = decodedArgs.message; - if (!fullMessageHex) continue; - - const unsignedMessageBytes = Buffer.from(fullMessageHex.slice(2), 'hex'); - const addressedCall = extractAddressedCall(unsignedMessageBytes); - if (addressedCall.length === 0) continue; - - // Check TypeID within AddressedCall for RegisterL1ValidatorMessage - if (addressedCall.length < 6) continue; - const acTypeID = (addressedCall[2] << 24) | (addressedCall[3] << 16) | (addressedCall[4] << 8) | addressedCall[5]; - const REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID_IN_AC = 1; - if (acTypeID !== REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID_IN_AC) { - continue; - } - - const payloadBytes = extractPayloadFromAddressedCall(addressedCall); - if (!payloadBytes) continue; - - try { - // Unpack the payload - const parsedPayload: SolidityValidationPeriod = unpackRegisterL1ValidatorPayload(payloadBytes); - // Calculate the validationID (hash) of this message payload - const logValidationIDBytes = calculateValidationID(parsedPayload); - - // Compare the calculated hash with the target validation ID - if (compareBytes(logValidationIDBytes, targetValidationIDBytes)) { - - // Optional: Confirm the nodeID in the message matches the input nodeID - if (targetNodeIDBytes && !compareBytes(parsedPayload.nodeID, targetNodeIDBytes)) { - console.warn(`ValidationID match found (${validationIDHex}) in log ${log.transactionHash}, but NodeID in message (${utils.base58check.encode(Buffer.from(parsedPayload.nodeID))}) does not match expected NodeID ${nodeID}. Skipping.`); - continue; - } - - // Construct justification using the original payloadBytes - const tag = new Uint8Array([0x12]); // Field 2, wire type 2 - const lengthVarint = encodeVarint(payloadBytes.length); - const marshalledJustification = new Uint8Array(tag.length + lengthVarint.length + payloadBytes.length); - marshalledJustification.set(tag, 0); - marshalledJustification.set(lengthVarint, tag.length); - marshalledJustification.set(payloadBytes, tag.length + lengthVarint.length); - - console.log(`Found matching ValidationID ${validationIDHex} (NodeID ${nodeID}) in Warp log (Tx: ${log.transactionHash}). Marshalled justification.`); - return marshalledJustification; + const CHUNK_SIZE = 10_000; // Number of blocks to query in each chunk + const MAX_CHUNKS = 100; // Maximum number of chunks to try (to prevent infinite loops) + let toBlock: bigint | number | 'latest' = 'latest'; + let foundMatch = false; + let marshalledJustification: Uint8Array | null = null; + let chunksSearched = 0; + + console.log(`Searching Warp logs in chunks of ${CHUNK_SIZE} blocks starting from latest...`); + + while (!foundMatch && chunksSearched < MAX_CHUNKS) { + // Get the current block number for this iteration + const latestBlockNum = toBlock === 'latest' + ? await publicClient.getBlockNumber() + : toBlock; + + // Calculate the fromBlock for this chunk + const fromBlockNum = Math.max(0, Number(latestBlockNum) - CHUNK_SIZE); + + console.log(`Searching blocks ${fromBlockNum} to ${toBlock === 'latest' ? latestBlockNum : toBlock}...`); + + const warpLogs = await publicClient.getLogs({ + address: WARP_ADDRESS, + event: sendWarpMessageEventAbi, + fromBlock: BigInt(fromBlockNum), + toBlock: toBlock === 'latest' ? toBlock : BigInt(Number(toBlock)), + }); + + if (warpLogs.length > 0) { + console.log(`Found ${warpLogs.length} Warp logs in current chunk. Searching for ValidationID ${validationIDHex}...`); + + for (const log of warpLogs) { + try { + const decodedArgs = log.args as { message?: `0x${string}` }; + const fullMessageHex = decodedArgs.message; + if (!fullMessageHex) continue; + + const unsignedMessageBytes = Buffer.from(fullMessageHex.slice(2), 'hex'); + const addressedCall = extractAddressedCall(unsignedMessageBytes); + if (addressedCall.length === 0) continue; + + // Check TypeID within AddressedCall for RegisterL1ValidatorMessage + if (addressedCall.length < 6) continue; + const acTypeID = (addressedCall[2] << 24) | (addressedCall[3] << 16) | (addressedCall[4] << 8) | addressedCall[5]; + const REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID_IN_AC = 1; + if (acTypeID !== REGISTER_L1_VALIDATOR_MESSAGE_TYPE_ID_IN_AC) { + continue; + } + + const payloadBytes = extractPayloadFromAddressedCall(addressedCall); + if (!payloadBytes) continue; + + try { + // Unpack the payload + const parsedPayload: SolidityValidationPeriod = unpackRegisterL1ValidatorPayload(payloadBytes); + // Calculate the validationID (hash) of this message payload + const logValidationIDBytes = calculateValidationID(parsedPayload); + + // Compare the calculated hash with the target validation ID + if (compareBytes(logValidationIDBytes, targetValidationIDBytes)) { + + // Optional: Confirm the nodeID in the message matches the input nodeID + if (targetNodeIDBytes && !compareBytes(parsedPayload.nodeID, targetNodeIDBytes)) { + console.warn(`ValidationID match found (${validationIDHex}) in log ${log.transactionHash}, but NodeID in message (${utils.base58check.encode(Buffer.from(parsedPayload.nodeID))}) does not match expected NodeID ${nodeID}. Skipping.`); + continue; + } + + // Construct justification using the original payloadBytes + const tag = new Uint8Array([0x12]); // Field 2, wire type 2 + const lengthVarint = encodeVarint(payloadBytes.length); + marshalledJustification = new Uint8Array(tag.length + lengthVarint.length + payloadBytes.length); + marshalledJustification.set(tag, 0); + marshalledJustification.set(lengthVarint, tag.length); + marshalledJustification.set(payloadBytes, tag.length + lengthVarint.length); + + console.log(`Found matching ValidationID ${validationIDHex} (NodeID ${nodeID}) in Warp log (Tx: ${log.transactionHash}). Marshalled justification.`); + foundMatch = true; + break; + } + } catch (parseOrHashError) { + // console.warn(`Error parsing/hashing RegisterL1ValidatorMessage payload from Tx ${log.transactionHash}:`, parseOrHashError); + } + } catch (logProcessingError) { + console.error(`Error processing log entry for tx ${log.transactionHash}:`, logProcessingError); } - } catch (parseOrHashError) { - // console.warn(`Error parsing/hashing RegisterL1ValidatorMessage payload from Tx ${log.transactionHash}:`, parseOrHashError); } - } catch (logProcessingError) { - console.error(`Error processing log entry for tx ${log.transactionHash}:`, logProcessingError); + } else { + console.log(`No Warp logs found in blocks ${fromBlockNum} to ${toBlock === 'latest' ? latestBlockNum : toBlock}.`); } + + // Exit the loop if we found a match + if (foundMatch) break; + + // Move to the previous chunk + toBlock = fromBlockNum - 1; + + // Stop if we've reached the genesis block + if (toBlock <= 0) { + console.log(`Reached genesis block. Search complete.`); + break; + } + + chunksSearched++; } - - console.log(`No matching registration log found for ValidationID ${validationIDHex} in Warp logs.`); - return null; + + if (!foundMatch) { + console.log(`No matching registration log found for ValidationID ${validationIDHex} after searching ${chunksSearched} chunks.`); + } + + return marshalledJustification; } catch (fetchLogError) { console.error(`Error fetching or decoding logs for ValidationID ${validationIDHex}:`, fetchLogError); diff --git a/toolbox/src/toolbox/components/SelectSubnetId.tsx b/toolbox/src/toolbox/components/SelectSubnetId.tsx new file mode 100644 index 00000000000..a6499bef6a1 --- /dev/null +++ b/toolbox/src/toolbox/components/SelectSubnetId.tsx @@ -0,0 +1,40 @@ +import { Input, type Suggestion } from "../../components/Input"; +import { useCreateChainStore, useL1ListStore } from "../toolboxStore"; +import { useMemo } from "react"; + +export default function InputSubnetId({ value, onChange, error }: { value: string, onChange: (value: string) => void, error?: string | null }) { + const createChainStoreSubnetId = useCreateChainStore()(state => state.subnetId); + const l1List = useL1ListStore()(state => state.l1List); + + const subnetIdSuggestions: Suggestion[] = useMemo(() => { + const result: Suggestion[] = []; + + if (createChainStoreSubnetId) { + result.push({ + title: createChainStoreSubnetId, + value: createChainStoreSubnetId, + description: "From the \"Create Subnet\" tool" + }); + } + + for (const l1 of l1List) { + if (l1.subnetId) { + result.push({ + title: `${l1.name} (${l1.subnetId})`, + value: l1.subnetId, + description: "From your chain list" + }); + } + } + + return result; + }, [createChainStoreSubnetId, l1List]); + + return +}