Skip to content
6 changes: 5 additions & 1 deletion apps/namadillo/src/App/Masp/MaspShield.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import invariant from "invariant";
import { useAtom, useAtomValue } from "jotai";
import { createTransferDataFromNamada } from "lib/transactions";
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { Link, useLocation } from "react-router-dom";
import { AssetWithAmountAndChain } from "types";
interface MaspShieldProps {
sourceAddress: string;
Expand All @@ -35,6 +35,7 @@ export const MaspShield = ({
setAssetSelectorModalOpen,
}: MaspShieldProps): JSX.Element => {
// COMPONENT STATE
const [memo, setMemo] = useState("");
const [displayAmount, setDisplayAmount] = useAtom(transferAmountAtom);
const [selectedAssetWithAmount, setSelectedAssetWithAmount] = useState<
AssetWithAmountAndChain | undefined
Expand All @@ -49,6 +50,7 @@ export const MaspShield = ({
const chainParameters = useAtomValue(chainParametersAtom);
const defaultAccounts = useAtomValue(allDefaultAccountsAtom);
const [ledgerStatus, setLedgerStatusStop] = useAtom(ledgerStatusDataAtom);
const { pathname } = useLocation();
// DERIVED VALUES
const transparentAddress = defaultAccounts.data?.find(
(account) => account.type !== AccountType.ShieldedKeys
Expand Down Expand Up @@ -167,6 +169,8 @@ export const MaspShield = ({
address: destinationAddress,
isShieldedAddress: true,
onChangeAddress: setDestinationAddress,
memo,
onChangeMemo: pathname !== routes.shield ? setMemo : undefined,
}}
feeProps={feeProps}
isSubmitting={isPerformingTransfer || isSuccess}
Expand Down
3 changes: 3 additions & 0 deletions apps/namadillo/src/App/Masp/MaspUnshield.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const MaspUnshield = ({
setAssetSelectorModalOpen,
}: MaspUnshieldProps): JSX.Element => {
// COMPONENT STATE
const [memo, setMemo] = useState("");
const [displayAmount, setDisplayAmount] = useAtom(transferAmountAtom);
const [selectedAssetWithAmount, setSelectedAssetWithAmount] = useState<
AssetWithAmountAndChain | undefined
Expand Down Expand Up @@ -148,6 +149,8 @@ export const MaspUnshield = ({
address: destinationAddress,
isShieldedAddress: false,
onChangeAddress: setDestinationAddress,
memo,
onChangeMemo: setMemo,
}}
feeProps={feeProps}
isSubmitting={isPerformingTransfer || isSuccess}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ jest.mock("hooks/useIsChannelInactive", () => ({
})),
}));

// Mock the getAvailableChains function
jest.mock("atoms/integrations", () => ({
...jest.requireActual("atoms/integrations"),
getAvailableChains: jest.fn(() => []),
}));

describe("SwapSelectAssetModal", () => {
const onCloseMock = jest.fn();
const onSelectMock = jest.fn();
Expand Down
17 changes: 14 additions & 3 deletions apps/namadillo/src/App/Transfer/DestinationAddressModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import { useCallback, useState } from "react";
import { Address, Asset } from "types";
import namadaShieldedIcon from "./assets/namada-shielded.svg";
import namadaTransparentIcon from "./assets/namada-transparent.svg";
import { isShieldedAddress, isTransparentAddress } from "./common";
import {
isIbcAddress,
isShieldedAddress,
isTransparentAddress,
} from "./common";

export type ValidationError = {
type: "invalid-format" | "unsupported-chain" | "empty";
Expand Down Expand Up @@ -66,6 +70,9 @@ export const DestinationAddressModal = ({
// Dont display an address if it matches the source address
const isSourceAddressMatch = (address: string): boolean =>
address === sourceAddress;
const isSourceIbc = isIbcAddress(sourceAddress);
const filterNonIbcIfSourceIbc = (items: RecentAddress[]): RecentAddress[] =>
isSourceIbc ? items.filter((item) => item.type !== "ibc") : items;

// Build your addresses options
const addressOptions: AddressOption[] = [];
Expand Down Expand Up @@ -96,7 +103,7 @@ export const DestinationAddressModal = ({
});
}
}
if (keplrAddress)
if (keplrAddress && !isSourceIbc) {
addressOptions.push({
id: "keplr",
label: "Keplr Address",
Expand All @@ -107,11 +114,15 @@ export const DestinationAddressModal = ({
: getChainImageUrl(getChainFromAddress(keplrAddress ?? "")),
type: "keplr",
});
}

const addressOptionsAddresses = addressOptions.map((addr) => addr.address);

// Build recent addresses options
const recentAddressOptions: AddressOption[] = recentAddresses
const filteredRecentAddresses = filterNonIbcIfSourceIbc(recentAddresses);
const recentAddressOptions: AddressOption[] = filteredRecentAddresses
.filter((addresses) => !addressOptionsAddresses.includes(addresses.address))
.filter((addresses) => addresses.address !== sourceAddress)
.map((recent) => ({
id: `recent-${recent.address}`,
label: recent.label || getAddressLabel(recent.address, recent.type),
Expand Down
7 changes: 1 addition & 6 deletions apps/namadillo/src/App/Transfer/SelectToken.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,18 +136,13 @@ export const SelectToken = ({
try {
const keplrInstance = await keplrWallet.get();
// Keplr is not installed, redirect to download page
if (!keplrInstance) {
keplrWallet.install();
return;
}

if (!keplrInstance) return keplrWallet.install();
const targetChainRegistry = getChainRegistryByChainName(
token.chainName
);
invariant(targetChainRegistry, "Target chain registry not found");
const chainId = targetChainRegistry.chain.chain_id;
await connectToChainId(chainId);

// Update connected wallets state only after successful connection
setConnectedWallets((obj: Record<string, boolean>) => ({
...obj,
Expand Down
15 changes: 12 additions & 3 deletions apps/namadillo/src/App/Transfer/TransferDestination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type TransferDestinationProps = {
sourceAsset: Asset | undefined;
onChangeAddress?: (address: Address) => void;
onChangeMemo?: (address: string) => void;
isShielding?: boolean;
isUnshielding?: boolean;
};

export const TransferDestination = ({
Expand All @@ -68,6 +70,8 @@ export const TransferDestination = ({
sourceAsset,
onChangeAddress,
onChangeMemo,
isShielding = false,
isUnshielding = false,
}: TransferDestinationProps): JSX.Element => {
const { data: accounts } = useAtomValue(allDefaultAccountsAtom);
const [isModalOpen, setIsModalOpen] = useState(false);
Expand Down Expand Up @@ -126,6 +130,12 @@ export const TransferDestination = ({
undefined
: destinationAddress;

// Check if it's an internal shielding or unshielding transaction
const isInternalShieldingOrUnshielding =
(isShielding || isUnshielding) &&
isNamadaAddress(sourceAddress ?? "") &&
isNamadaAddress(destinationAddress ?? "");

const sourceWallet =
isNamadaAddress(destinationAddress || "") ? wallets.namada : wallets.keplr;
const addressType =
Expand Down Expand Up @@ -228,7 +238,7 @@ export const TransferDestination = ({
getChainFromAddress(destinationAddress ?? "")
?.pretty_name
}
className="w-7"
className="w-10"
/>
<div className="flex flex-col ml-4">
<div className="flex flex-col">
Expand All @@ -251,8 +261,7 @@ export const TransferDestination = ({
</button>
}
</div>

{customAddress && (
{(customAddress || isInternalShieldingOrUnshielding) && (
<Stack gap={8}>
<CustomAddressForm memo={memo} onChangeMemo={onChangeMemo} />
</Stack>
Expand Down
2 changes: 2 additions & 0 deletions apps/namadillo/src/App/Transfer/TransferModule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ export const TransferModule = ({
destinationAsset={selectedAsset?.asset}
amount={source.amount}
isSubmitting={isSubmitting}
isShielding={isShielding}
isUnshielding={isUnshielding}
/>
{ibcTransfer && requiresIbcChannels && ibcChannels && (
<IbcChannels
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { namadaChainMock, randomChainMock } from "App/Common/__mocks__/chains";
import { walletMock } from "App/Common/__mocks__/providers";
import { SelectChainModal } from "App/Transfer/SelectChainModal";

// Mock the getAvailableChains function
jest.mock("atoms/integrations", () => ({
...jest.requireActual("atoms/integrations"),
getAvailableChains: jest.fn(() => []),
}));

describe("Component: SelectChainModal", () => {
const mockChains = [randomChainMock, namadaChainMock];
const mockAddress = "cosmos1xnu3p06fkke8hnl7t83hzhggrca59syf0wjqgh";
Expand Down
11 changes: 5 additions & 6 deletions apps/namadillo/src/atoms/integrations/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,20 +150,19 @@ export const allKeplrAssetsBalanceAtom = atomWithQuery<
chainAssetsMap.data,
connectedWallets,
],
staleTime: 30_000, // Consider data fresh for 30 seconds
gcTime: 5 * 60_000, // Keep in cache for 5 minutes after last use
refetchOnMount: false, // Don't refetch if data is fresh
refetchOnWindowFocus: false, // Don't refetch on window focus
...queryDependentFn(async () => {
invariant(chainSettings.data, "No chain settings");
invariant(chainAssetsMap.data, "No chain assets map");

// Only proceed if Keplr is connected
if (!connectedWallets?.keplr) {
return {};
}

if (!connectedWallets?.keplr) return {};
const availableChains = getAvailableChains();

// Get Keplr wallet instance
const keplr = getKeplrWallet();

// First, silently check which chains we're already connected to
const connectedChains: { chain: Chain; walletAddress: string }[] = [];

Expand Down
Empty file removed apps/namadillo/src/constants.ts
Empty file.
16 changes: 13 additions & 3 deletions apps/namadillo/src/integrations/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import tokenImage from "App/Common/assets/token.svg";
import namadaTransparentSvg from "App/Transfer/assets/namada-transparent.svg";
import { isShieldedAddress, isTransparentAddress } from "App/Transfer/common";
import {
getAvailableChains,
getChainRegistryByChainName,
getRestApiAddressByIndex,
getRpcByIndex,
} from "atoms/integrations";
import BigNumber from "bignumber.js";
import { chains } from "chain-registry";

import {
Asset,
Expand Down Expand Up @@ -44,12 +44,22 @@ export const findRegistryByChainId = (
};

export const getChainFromAddress = (address: string): Chain | undefined => {
const chains = getAvailableChains();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stride wasn't selectable b/c here I was using the chains from the registry and it was getting matched with the stratos chain instead of stride.

if (isShieldedAddress(address) || isTransparentAddress(address)) {
return chains.find((chain) => chain.chain_name === "namada") as Chain;
} else {
// Connect to IBC chain and then return the registered chain
const chain = chains.find(
(chain) => chain.bech32_prefix && address.startsWith(chain.bech32_prefix)
// Sort chains by bech32_prefix length (longest first) to avoid prefix collision
// e.g., "noble" should be checked before "n" (NYM) to prevent mismatches
const sortedChains = chains
.filter((chain) => chain.bech32_prefix)
.sort(
(a, b) =>
(b.bech32_prefix?.length || 0) - (a.bech32_prefix?.length || 0)
);

const chain = sortedChains.find((chain) =>
address.startsWith(chain.bech32_prefix + "1")
);
return chain as Chain | undefined;
}
Expand Down