From e0a12fbdc8e2d91b89e41c1f97692402ab859929 Mon Sep 17 00:00:00 2001 From: Mateusz Jasiuk Date: Tue, 18 Nov 2025 14:13:05 +0100 Subject: [PATCH 1/4] fix: wrong ibc channels for withdraw --- apps/namadillo/src/App/Transfer/SelectToken.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/namadillo/src/App/Transfer/SelectToken.tsx b/apps/namadillo/src/App/Transfer/SelectToken.tsx index e6bc91dbad..46899bec7d 100644 --- a/apps/namadillo/src/App/Transfer/SelectToken.tsx +++ b/apps/namadillo/src/App/Transfer/SelectToken.tsx @@ -7,6 +7,7 @@ import { connectedWalletsAtom, getChainRegistryByChainName, namadaRegistryChainAssetsMapAtom, + SUPPORTED_ASSETS_MAP, } from "atoms/integrations"; import { tokenPricesFamily } from "atoms/prices/atoms"; import clsx from "clsx"; @@ -163,6 +164,20 @@ export const SelectToken = ({ } finally { setIsConnectingKeplr(false); } + } else { + // Because IbcWithdraw uses registry from KeplrWalletManager, we need to connect to + // the source chain of the selected asset. Otherwise channels may not be correct as + // ibcChannelsFamily relies on connected registry. + // TODO: this will also run if the target chain is namada, which is not necessary + const chainName = [...SUPPORTED_ASSETS_MAP.entries()].find( + ([_, assetSymbols]) => { + return assetSymbols.includes(token.asset.symbol); + } + )?.[0]; + invariant(chainName, "Chain name not found for selected asset"); + const registry = getChainRegistryByChainName(chainName); + invariant(registry, "Chain registry not found for counterparty"); + await connectToChainId(registry.chain.chain_id); } onSelect?.(token, newSourceAddress); From cf56f1b8d6f1055d755fa78b50f2c88ddc1717e0 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Wed, 19 Nov 2025 16:45:43 +1300 Subject: [PATCH 2/4] fix: add ibc check --- apps/namadillo/src/App/Transfer/SelectToken.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/namadillo/src/App/Transfer/SelectToken.tsx b/apps/namadillo/src/App/Transfer/SelectToken.tsx index 46899bec7d..4921bb71cd 100644 --- a/apps/namadillo/src/App/Transfer/SelectToken.tsx +++ b/apps/namadillo/src/App/Transfer/SelectToken.tsx @@ -126,6 +126,7 @@ export const SelectToken = ({ async (token: AssetWithAmountAndChain): Promise => { // Check if current address is Keplr and if we need to connect to specific chain for this token const isIbcOrKeplrToken = !isNamadaAddress(sourceAddress); + const destinationIsIbcOrKeplrToken = !isNamadaAddress(destinationAddress); // only used for IBC tokens let newSourceAddress: string | undefined; try { @@ -164,11 +165,10 @@ export const SelectToken = ({ } finally { setIsConnectingKeplr(false); } - } else { + } else if (destinationIsIbcOrKeplrToken) { // Because IbcWithdraw uses registry from KeplrWalletManager, we need to connect to // the source chain of the selected asset. Otherwise channels may not be correct as // ibcChannelsFamily relies on connected registry. - // TODO: this will also run if the target chain is namada, which is not necessary const chainName = [...SUPPORTED_ASSETS_MAP.entries()].find( ([_, assetSymbols]) => { return assetSymbols.includes(token.asset.symbol); From 2ae4cb3d293bf0c847cace9f9b346feb9f7fd645 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Wed, 19 Nov 2025 17:38:58 +1300 Subject: [PATCH 3/4] fix: add useEffect so the connection is done for the URLs too --- .../src/App/Transfer/TransferLayout.tsx | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/apps/namadillo/src/App/Transfer/TransferLayout.tsx b/apps/namadillo/src/App/Transfer/TransferLayout.tsx index 64b56eb6ed..6927974651 100644 --- a/apps/namadillo/src/App/Transfer/TransferLayout.tsx +++ b/apps/namadillo/src/App/Transfer/TransferLayout.tsx @@ -11,15 +11,18 @@ import { shieldedBalanceAtom } from "atoms/balance"; import { connectedWalletsAtom } from "atoms/integrations"; import { useUserHasAccount } from "hooks/useIsAuthenticated"; import { useUrlState, useUrlStateBatch } from "hooks/useUrlState"; +import { useWalletManager } from "hooks/useWalletManager"; import { KeplrWalletManager } from "integrations/Keplr"; import { getChainFromAddress } from "integrations/utils"; import { useAtomValue } from "jotai"; import { useEffect, useRef, useState } from "react"; -import { isNamadaAddress } from "./common"; +import { isIbcAddress, isNamadaAddress } from "./common"; import { determineTransferType } from "./utils"; export const TransferLayout: React.FC = () => { const keplrWalletManager = new KeplrWalletManager(); + const { connectToChainId } = useWalletManager(keplrWalletManager); + const userHasAccount = useUserHasAccount(); const [sourceAddressUrl, setSourceAddressUrl] = useUrlState("source"); const [destinationAddressUrl, setDestinationAddressUrl] = @@ -59,12 +62,28 @@ export const TransferLayout: React.FC = () => { // Refetch shielded balance for MASP operations useEffect(() => { - if (transferType === "shield" || transferType === "unshield") { - refetchShieldedBalance(); - } + if (["shield", "unshield"].includes(transferType)) refetchShieldedBalance(); }, [transferType, refetchShieldedBalance]); + // Connect to IBC chain if source or destination address is an IBC address + useEffect(() => { + const connectIfIbc = async (address: string): Promise => { + const chain = getChainFromAddress(address); + if (chain?.chain_id) { + try { + await connectToChainId(chain.chain_id); + } catch (error) { + console.error("Failed to connect to IBC chain:", error); + } + } + }; + + if (isIbcAddress(sourceAddress)) connectIfIbc(sourceAddress); + else if (isIbcAddress(destinationAddress)) connectIfIbc(destinationAddress); + }, [sourceAddress, destinationAddress, connectToChainId]); + // Validate source address - check if it's from keyring or Keplr + // If not it means the address is invalid at best, poisoned at worst. useEffect(() => { const validateSourceAddress = async (): Promise => { if (!sourceAddressUrl || !userHasAccount || !accounts) return; From 748779709bf91daec26b0a49222fd8a452d36cf4 Mon Sep 17 00:00:00 2001 From: neocybereth Date: Thu, 20 Nov 2025 00:29:11 +1300 Subject: [PATCH 4/4] fix: cleanup --- apps/namadillo/src/App/Ibc/IbcWithdraw.tsx | 20 +++++++++++++++++- .../src/App/Transfer/TransferLayout.tsx | 21 +------------------ 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/apps/namadillo/src/App/Ibc/IbcWithdraw.tsx b/apps/namadillo/src/App/Ibc/IbcWithdraw.tsx index 00731c59dd..cf8a21eaba 100644 --- a/apps/namadillo/src/App/Ibc/IbcWithdraw.tsx +++ b/apps/namadillo/src/App/Ibc/IbcWithdraw.tsx @@ -2,7 +2,7 @@ import { IbcTransferProps } from "@namada/sdk-multicore"; import { AccountType } from "@namada/types"; import { mapUndefined } from "@namada/utils"; import { routes } from "App/routes"; -import { isShieldedAddress } from "App/Transfer/common"; +import { isIbcAddress, isShieldedAddress } from "App/Transfer/common"; import { TransferModule } from "App/Transfer/TransferModule"; import { OnSubmitTransferParams } from "App/Transfer/types"; import { @@ -28,6 +28,7 @@ import { useTransaction } from "hooks/useTransaction"; import { useTransactionActions } from "hooks/useTransactionActions"; import { useWalletManager } from "hooks/useWalletManager"; import { KeplrWalletManager } from "integrations/Keplr"; +import { getChainFromAddress } from "integrations/utils"; import invariant from "invariant"; import { useAtom, useAtomValue } from "jotai"; import { TransactionPair } from "lib/query"; @@ -84,6 +85,7 @@ export const IbcWithdraw = ({ walletAddress: keplrAddress, chainId, registry, + connectToChainId, } = useWalletManager(keplrWalletManager); const transparentAccount = useAtomValue(defaultAccountAtom); const namadaChain = useAtomValue(chainAtom); @@ -142,6 +144,22 @@ export const IbcWithdraw = ({ } }; + // Connect to IBC chain if destination address is an IBC address + useEffect(() => { + const connectIfIbc = async (address: string): Promise => { + const chain = getChainFromAddress(address); + if (chain?.chain_id) { + try { + await connectToChainId(chain.chain_id); + } catch (error) { + console.error("Failed to connect to IBC chain:", error); + } + } + }; + + if (isIbcAddress(destinationAddress)) connectIfIbc(destinationAddress); + }, [destinationAddress]); + const { data: ibcChannels, isError: unknownIbcChannels, diff --git a/apps/namadillo/src/App/Transfer/TransferLayout.tsx b/apps/namadillo/src/App/Transfer/TransferLayout.tsx index 6927974651..8a4c856a54 100644 --- a/apps/namadillo/src/App/Transfer/TransferLayout.tsx +++ b/apps/namadillo/src/App/Transfer/TransferLayout.tsx @@ -11,17 +11,15 @@ import { shieldedBalanceAtom } from "atoms/balance"; import { connectedWalletsAtom } from "atoms/integrations"; import { useUserHasAccount } from "hooks/useIsAuthenticated"; import { useUrlState, useUrlStateBatch } from "hooks/useUrlState"; -import { useWalletManager } from "hooks/useWalletManager"; import { KeplrWalletManager } from "integrations/Keplr"; import { getChainFromAddress } from "integrations/utils"; import { useAtomValue } from "jotai"; import { useEffect, useRef, useState } from "react"; -import { isIbcAddress, isNamadaAddress } from "./common"; +import { isNamadaAddress } from "./common"; import { determineTransferType } from "./utils"; export const TransferLayout: React.FC = () => { const keplrWalletManager = new KeplrWalletManager(); - const { connectToChainId } = useWalletManager(keplrWalletManager); const userHasAccount = useUserHasAccount(); const [sourceAddressUrl, setSourceAddressUrl] = useUrlState("source"); @@ -65,23 +63,6 @@ export const TransferLayout: React.FC = () => { if (["shield", "unshield"].includes(transferType)) refetchShieldedBalance(); }, [transferType, refetchShieldedBalance]); - // Connect to IBC chain if source or destination address is an IBC address - useEffect(() => { - const connectIfIbc = async (address: string): Promise => { - const chain = getChainFromAddress(address); - if (chain?.chain_id) { - try { - await connectToChainId(chain.chain_id); - } catch (error) { - console.error("Failed to connect to IBC chain:", error); - } - } - }; - - if (isIbcAddress(sourceAddress)) connectIfIbc(sourceAddress); - else if (isIbcAddress(destinationAddress)) connectIfIbc(destinationAddress); - }, [sourceAddress, destinationAddress, connectToChainId]); - // Validate source address - check if it's from keyring or Keplr // If not it means the address is invalid at best, poisoned at worst. useEffect(() => {