Skip to content

Commit

Permalink
feat: limit ibc withdrawal of assets only for the same chain (#1783)
Browse files Browse the repository at this point in the history
* feat: limit ibc withdrawal only for the same chain

* fix: fixing unit test and adding another one for non-selectable chains

* fix: fixing tests on TransferDestination
  • Loading branch information
pedrorezende authored Feb 27, 2025
1 parent b590569 commit 8a960b1
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 16 deletions.
42 changes: 30 additions & 12 deletions apps/namadillo/src/App/Ibc/IbcWithdraw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import {
} from "App/Transfer/TransferModule";
import { defaultAccountAtom } from "atoms/accounts";
import { namadaTransparentAssetsAtom } from "atoms/balance";
import { chainAtom } from "atoms/chain";
import { chainAtom, chainTokensAtom } from "atoms/chain";
import {
availableChainsAtom,
chainRegistryAtom,
createIbcTxAtom,
getDenomFromIbcTrace,
ibcChannelsFamily,
searchChainByDenom,
} from "atoms/integrations";
import BigNumber from "bignumber.js";
import { useTransaction } from "hooks/useTransaction";
Expand All @@ -40,8 +40,6 @@ const keplr = new KeplrWalletManager();

export const IbcWithdraw: React.FC = () => {
const namadaAccount = useAtomValue(defaultAccountAtom);
const chainRegistry = useAtomValue(chainRegistryAtom);
const availableChains = useAtomValue(availableChainsAtom);
const namadaChain = useAtomValue(chainAtom);

const [generalErrorMessage, setGeneralErrorMessage] = useState("");
Expand All @@ -53,6 +51,9 @@ export const IbcWithdraw: React.FC = () => {
const [statusExplanation, setStatusExplanation] = useState("");
const [completedAt, setCompletedAt] = useState<Date | undefined>();
const [txHash, setTxHash] = useState<string | undefined>();
const [destinationChain, setDestinationChain] = useState<Chain | undefined>();

const chainTokens = useAtomValue(chainTokensAtom);

const { data: availableAssets } = useAtomValue(namadaTransparentAssetsAtom);
const { storeTransaction } = useTransactionActions();
Expand Down Expand Up @@ -83,10 +84,6 @@ export const IbcWithdraw: React.FC = () => {
connectToChainId(defaultChainId);
};

const onChangeChain = (chain: Chain): void => {
connectToChainId(chain.chain_id);
};

useTransactionEventListener("IbcWithdraw.Success", (e) => {
if (txHash && e.detail.hash === txHash) {
setCompletedAt(new Date());
Expand All @@ -109,6 +106,29 @@ export const IbcWithdraw: React.FC = () => {
setSourceChannel(ibcChannels?.namadaChannel || "");
}, [ibcChannels]);

// Search for original chain. We don't want to enable users to transfer Namada assets
// to other chains different than the original one. Ex: OSMO should only be withdrew to Osmosis,
// ATOM to Cosmoshub.
useEffect(() => {
if (!selectedAsset || !chainTokens.data) {
setDestinationChain(undefined);
return;
}

const token = chainTokens.data.find(
(token) => token.address === selectedAsset.originalAddress
);

if (token && "trace" in token) {
const denom = getDenomFromIbcTrace(token.trace);
const chain = searchChainByDenom(denom);
setDestinationChain(chain);
return;
}

setDestinationChain(undefined);
}, [selectedAsset, chainTokens.data]);

const {
execute: performWithdraw,
feeProps,
Expand Down Expand Up @@ -258,13 +278,11 @@ export const IbcWithdraw: React.FC = () => {
wallet: wallets.keplr,
walletAddress: keplrAddress,
availableWallets: [wallets.keplr],
availableChains,
enableCustomAddress: true,
customAddress,
onChangeCustomAddress: setCustomAddress,
chain: mapUndefined((id) => chainRegistry[id]?.chain, chainId),
chain: destinationChain,
onChangeWallet,
onChangeChain,
isShielded: false,
}}
errorMessage={generalErrorMessage || error?.message || ""}
Expand Down
7 changes: 5 additions & 2 deletions apps/namadillo/src/App/Transfer/SelectedChain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ export const SelectedChain = ({
{(!wallet || !chain) && (
<span className={selectorClassList}>
<EmptyResourceIcon className="w-7" />
Select chain
<GoChevronDown className="text-sm" />
{onClick ?
<>
Select chain <GoChevronDown className="text-sm" />
</>
: <span className="bg-neutral-900 w-20 h-7 rounded-sm opacity-30" />}
</span>
)}
{wallet && chain && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ import { parseChainInfo } from "../common";
describe("Component: TransferDestination", () => {
it("should render the component with the default props", () => {
render(<TransferDestination />);
expect(screen.getByText(/select chain/i)).toBeInTheDocument();
const chainButton = screen.queryByText(/select chain/i);
expect(chainButton).not.toBeInTheDocument();
});

it("should render the component with chain selectable", () => {
render(<TransferDestination openChainSelector={jest.fn()} />);
const chainButton = screen.getByText(/select chain/i);
expect(chainButton).toBeInTheDocument();
});

it("should render the TabSelector for shielded/transparent when onChangeShielded is provided", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,24 @@ import { walletMock } from "../__mocks__/providers";
describe("Component: TransferSource", () => {
it("should render the component with the default props", () => {
render(
<TransferSource isConnected={false} openProviderSelector={jest.fn()} />
<TransferSource
isConnected={false}
openProviderSelector={jest.fn()}
openChainSelector={jest.fn()}
/>
);
expect(screen.getByText("Connect Wallet")).toBeInTheDocument();
expect(screen.getByText(/select chain/i)).toBeInTheDocument();
});

it("should not render chain selector when openChainSelector is not defined", () => {
render(
<TransferSource isConnected={false} openProviderSelector={jest.fn()} />
);
const selectChain = screen.queryByText(/selected chain/i);
expect(selectChain).not.toBeInTheDocument();
});

const setup = (props: Partial<TransferSourceProps> = {}): void => {
render(
<TransferSource
Expand Down
15 changes: 15 additions & 0 deletions apps/namadillo/src/atoms/integrations/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,3 +440,18 @@ export const addLocalnetToRegistry = (config: LocalnetToml): void => {
export const getDenomFromIbcTrace = (ibcAddress: string): string => {
return ibcAddress.replaceAll(/(transfer\/|channel-\d+\/)*/gi, "");
};

export const chainHasFeeTokenDenom = (chain: Chain, denom: string): boolean => {
return !!chain.fees?.fee_tokens?.some(
(feeToken) => feeToken.denom.toLowerCase() === denom.toLowerCase()
);
};

export const searchChainByDenom = (denom: string): Chain | undefined => {
return getKnownChains(false).find(
(chainRegistry: ChainRegistryEntry | undefined) => {
if (!chainRegistry) return false;
return chainHasFeeTokenDenom(chainRegistry.chain, denom);
}
)?.chain;
};

0 comments on commit 8a960b1

Please sign in to comment.