diff --git a/apps/maestro/src/config/env.ts b/apps/maestro/src/config/env.ts index 98a4300b..4668c50a 100644 --- a/apps/maestro/src/config/env.ts +++ b/apps/maestro/src/config/env.ts @@ -36,6 +36,10 @@ export const NEXT_PUBLIC_EXPLORER_URL = Maybe.of( process.env.NEXT_PUBLIC_EXPLORER_URL ).mapOr("", String); +export const NEXT_PUBLIC_WHITELISTED_DEST_CHAINS_FOR_VM = Maybe.of( + process.env.NEXT_PUBLIC_WHITELISTED_DEST_CHAINS_FOR_VM +).mapOr("", String); + export const NEXT_PUBLIC_FILE_BUG_REPORT_URL = Maybe.of( process.env.NEXT_PUBLIC_FILE_BUG_REPORT_URL ).mapOr("", String); diff --git a/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.state.ts b/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.state.ts index 0524726c..bfcd3f5c 100644 --- a/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.state.ts +++ b/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.state.ts @@ -71,7 +71,7 @@ export function useStep3ChainSelectionState() { isDeploying, totalGasFee, sourceChainId, - allChains, + chains: allChains, isEstimatingGasFees: isRemoteDeploymentGasFeeLoading, hasGasFeesEstimationError: isRemoteDeploymentGasFeeError, remoteDeploymentGasFees, diff --git a/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx b/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx index 2ce3b318..7cd3e937 100644 --- a/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx +++ b/apps/maestro/src/features/CanonicalTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx @@ -21,6 +21,7 @@ import { getNativeToken } from "~/lib/utils/getNativeToken"; import ChainPicker from "~/ui/compounds/ChainPicker"; import { NextButton, TokenNameAlert } from "~/ui/compounds/MultiStepForm"; import { useStep3ChainSelectionState } from "./DeployAndRegister.state"; +import { filterEligibleChains } from "~/lib/utils/chains"; export const Step3: FC = () => { const { state: rootState, actions: rootActions } = @@ -31,7 +32,7 @@ export const Step3: FC = () => { const chainId = useChainId(); // Support both EVM and VM chains - const sourceChain = state.allChains?.find((chain) => chain.chain_id === chainId); + const sourceChain = state.chains?.find((chain) => chain.chain_id === chainId); const [validDestinationChainIds, erroredDestinationChainIds] = useMemo( () => @@ -136,7 +137,9 @@ export const Step3: FC = () => { addTransaction, ] ); - const eligibleChains = state.allChains.filter(chain => chain.chain_id !== chainId); + + const eligibleChains = filterEligibleChains(state.chains, chainId); + const formSubmitRef = useRef>(null); const { address } = useAccount(); diff --git a/apps/maestro/src/features/InterchainTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx b/apps/maestro/src/features/InterchainTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx index 117bf3c8..66779d16 100644 --- a/apps/maestro/src/features/InterchainTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx +++ b/apps/maestro/src/features/InterchainTokenDeployment/steps/deploy-and-register/DeployAndRegister.tsx @@ -21,6 +21,7 @@ import { NextButton } from "~/ui/compounds/MultiStepForm"; import { useDeployAndRegisterRemoteInterchainTokenMutation } from "../../hooks"; import { useInterchainTokenDeploymentStateContainer } from "../../InterchainTokenDeployment.state"; import { useStep2ChainSelectionState } from "./DeployAndRegister.state"; +import { filterEligibleChains } from "~/lib/utils/chains"; export const Step2: FC = () => { const { state: rootState, actions: rootActions } = @@ -139,8 +140,8 @@ export const Step2: FC = () => { addTransaction, ] ); - - const eligibleChains = state.chains.filter(chain => chain.chain_id !== chainId); + + const eligibleChains = filterEligibleChains(state.chains, chainId); const formSubmitRef = useRef>(null); diff --git a/apps/maestro/src/lib/utils/chains.ts b/apps/maestro/src/lib/utils/chains.ts new file mode 100644 index 00000000..8c81ee85 --- /dev/null +++ b/apps/maestro/src/lib/utils/chains.ts @@ -0,0 +1,31 @@ +import { ITSChainConfig } from "@axelarjs/api"; +import { NEXT_PUBLIC_WHITELISTED_DEST_CHAINS_FOR_VM } from "~/config/env"; + +export const filterEligibleChains = ( + chains: ITSChainConfig[], + currentChainId: number, +): ITSChainConfig[] => { + // Early return if no chains to filter + if (!chains.length) return []; + + const currentChain = chains.find((chain) => chain.chain_id === currentChainId); + const isCurrentChainVM = currentChain?.chain_type === 'vm'; + + const whitelistedChains = NEXT_PUBLIC_WHITELISTED_DEST_CHAINS_FOR_VM.split(',').map(chain => chain.trim()); + + // Normalize whitelist check + const isAllChainsWhitelisted = whitelistedChains[0] === 'all'; + + return chains.filter((chain) => { + // Always filter out current chain + if (chain.chain_id === currentChainId) return false; + + // For EVM chains, check whitelist + if (isCurrentChainVM && chain.chain_type === 'evm') { + return isAllChainsWhitelisted || whitelistedChains.includes(chain.id); + } + + // Non-EVM chains are always included + return true; + }); +}; diff --git a/apps/maestro/src/ui/components/ChainsDropdown/ChainsDropdown.tsx b/apps/maestro/src/ui/components/ChainsDropdown/ChainsDropdown.tsx index e12f844c..222a64a7 100644 --- a/apps/maestro/src/ui/components/ChainsDropdown/ChainsDropdown.tsx +++ b/apps/maestro/src/ui/components/ChainsDropdown/ChainsDropdown.tsx @@ -1,5 +1,3 @@ -import type { EVMChainConfig, VMChainConfig } from "@axelarjs/api/axelarscan"; -import { Dropdown, HelpCircleIcon } from "@axelarjs/ui"; import { toast } from "@axelarjs/ui/toaster"; import { cn } from "@axelarjs/ui/utils"; import { Maybe } from "@axelarjs/utils"; @@ -18,6 +16,8 @@ import { useChainsDropdownContainer, withChainsDropdownProvider, } from "./ChainsDropdown.state"; +import { ITSChainConfig } from "@axelarjs/api"; +import { HelpCircleIcon, Dropdown } from "@axelarjs/ui"; const ICON_SIZES = { xs: 14, @@ -52,10 +52,8 @@ export const ChainIcon: FC<{ ); }; -type ChainConfig = EVMChainConfig | VMChainConfig; - type Props = { - chains?: ChainConfig[]; + chains?: ITSChainConfig[]; compact?: boolean; hideLabel?: boolean; disabled?: boolean; @@ -63,8 +61,8 @@ type Props = { chainIconClassName?: string; contentClassName?: string; renderTrigger?: () => React.ReactNode; - selectedChain?: ChainConfig; - onSelectChain?: (chain: ChainConfig | null) => void; + selectedChain?: ITSChainConfig; + onSelectChain?: (chain?: ITSChainConfig) => void; size?: keyof typeof ICON_SIZES; chainType?: "evm" | "vm"; }; @@ -217,7 +215,7 @@ const ChainsDropdown: FC = (props) => {