From b524a033a59a099c36ef4b09610f39d849790aab Mon Sep 17 00:00:00 2001 From: the letter L <134443988+turbocrime@users.noreply.github.com> Date: Tue, 6 Aug 2024 04:48:31 -0700 Subject: [PATCH] forbid um ibc out to celestia (#1629) * forbid um ibc out to celestia * changeset * filter in UI * filter asset in component instead * variable name * equality order * revert unused getter * refactor filter w/ selector * Adjust placeholder w/ selector --------- Co-authored-by: turbocrime Co-authored-by: Gabe Rodriguez --- .changeset/polite-otters-cheer.md | 5 ++ .../components/ibc/ibc-out/ibc-out-form.tsx | 27 +++--- apps/minifront/src/state/ibc-out.ts | 88 ++++++++----------- apps/minifront/src/state/index.ts | 28 +++--- 4 files changed, 71 insertions(+), 77 deletions(-) create mode 100644 .changeset/polite-otters-cheer.md diff --git a/.changeset/polite-otters-cheer.md b/.changeset/polite-otters-cheer.md new file mode 100644 index 0000000000..36843a1e22 --- /dev/null +++ b/.changeset/polite-otters-cheer.md @@ -0,0 +1,5 @@ +--- +'minifront': patch +--- + +forbid um ibc out to celestia diff --git a/apps/minifront/src/components/ibc/ibc-out/ibc-out-form.tsx b/apps/minifront/src/components/ibc/ibc-out/ibc-out-form.tsx index f6633603f5..f70bc5612f 100644 --- a/apps/minifront/src/components/ibc/ibc-out/ibc-out-form.tsx +++ b/apps/minifront/src/components/ibc/ibc-out/ibc-out-form.tsx @@ -3,8 +3,9 @@ import { Input } from '@repo/ui/components/ui/input'; import { ChainSelector } from './chain-selector'; import { useStore } from '../../../state'; import { - filterBalancesPerChain, + filteredIbcBalancesSelector, ibcOutSelector, + ibcPlaceholderSelector, ibcValidationErrors, } from '../../../state/ibc-out'; import InputToken from '../../shared/input-token'; @@ -12,10 +13,18 @@ import { InputBlock } from '../../shared/input-block'; import { LockOpen2Icon } from '@radix-ui/react-icons'; import { useAssets, useBalancesResponses, useStakingTokenMetadata } from '../../../state/shared'; +const useFilteredBalances = () => { + // Kick off requests + useStakingTokenMetadata(); + useAssets(); + useBalancesResponses(); + // ======================== + return useStore(filteredIbcBalancesSelector); +}; + export const IbcOutForm = () => { - const stakingTokenMetadata = useStakingTokenMetadata(); - const assets = useAssets(); - const balances = useBalancesResponses(); + const filteredBalances = useFilteredBalances(); + const { sendIbcWithdraw, destinationChainAddress, @@ -24,15 +33,9 @@ export const IbcOutForm = () => { setAmount, selection, setSelection, - chain, } = useStore(ibcOutSelector); - const filteredBalances = filterBalancesPerChain( - balances.data ?? [], - chain, - assets.data ?? [], - stakingTokenMetadata.data, - ); const validationErrors = useStore(ibcValidationErrors); + const placeholder = useStore(ibcPlaceholderSelector); return (
{ => (set, get) => set(state => { state.ibcOut.chain = chain; }); + + // After new chain is selected, the asset selection should also be updated separately + const initialSelection = filteredIbcBalancesSelector(get())[0]; + set(state => { + state.ibcOut.selection = initialSelection; + }); }, setDestinationChainAddress: addr => { set(state => { @@ -248,6 +254,9 @@ const unknownAddrIsValid = (chain: Chain | undefined, address: string): boolean return !!words && prefix === chain.addressPrefix; }; +// These chains do not allow IBC-in transfers unless the token is native to the chain +export const NATIVE_TRANSFERS_ONLY_CHAIN_IDS = ['celestia']; + /** * Filters the given IBC loader response balances by checking if any of the assets * in the balance view match the staking token's asset ID or are of the same ibc channel. @@ -272,58 +281,37 @@ export const filterBalancesPerChain = ( }) .map(m => m.penumbraAssetId!); - const assetIdsToCheck = [penumbraAssetId, ...assetsWithMatchingChannel]; + const assetIdsToCheck = [...assetsWithMatchingChannel]; + + if ( + chain?.chainId && + penumbraAssetId && + !NATIVE_TRANSFERS_ONLY_CHAIN_IDS.includes(chain.chainId) + ) { + assetIdsToCheck.push(penumbraAssetId); + } return allBalances.filter(({ balanceView }) => { - return assetIdsToCheck.some(assetId => assetId?.equals(getAssetIdFromValueView(balanceView))); + return assetIdsToCheck.some(assetId => assetId.equals(getAssetIdFromValueView(balanceView))); }); }; -export const ibcOutMiddleware: Middleware = f => (set, get, store) => { - const modifiedSetter: typeof set = (...args) => { - set(...args); - - const initialChain = get().ibcOut.chains.data?.[0]; - const shouldSetInitialChain = !!initialChain && !get().ibcOut.chain; - if (shouldSetInitialChain) { - set(state => ({ - ...state, - ibcOut: { - ...state.ibcOut, - chain: initialChain, - }, - })); - } - - const assets = get().shared.assets.data; - const balancesResponses = get().shared.balancesResponses.data; - const stakingTokenMetadata = get().shared.stakingTokenMetadata.data; - const shouldSetInitialSelection = - initialChain && - assets?.length && - balancesResponses?.length && - stakingTokenMetadata && - !get().ibcOut.selection; - - if (shouldSetInitialSelection) { - const initialSelection = filterBalancesPerChain( - balancesResponses, - initialChain, - assets, - stakingTokenMetadata, - )[0]; - - set(state => ({ - ...state, - ibcOut: { - ...state.ibcOut, - selection: initialSelection, - }, - })); - } - }; - - store.setState = modifiedSetter; +export const filteredIbcBalancesSelector = (state: AllSlices): BalancesResponse[] => { + return filterBalancesPerChain( + state.shared.balancesResponses.data ?? [], + state.ibcOut.chain, + state.shared.assets.data ?? [], + state.shared.stakingTokenMetadata.data, + ); +}; - return f(modifiedSetter, get, store); +export const ibcPlaceholderSelector = (state: AllSlices): string => { + const filteredBalances = filteredIbcBalancesSelector(state); + if (!state.ibcOut.chain) { + return 'Select a chain'; + } + if (filteredBalances.length === 0) { + return 'No balances to transfer'; + } + return 'Enter an amount'; }; diff --git a/apps/minifront/src/state/index.ts b/apps/minifront/src/state/index.ts index 4c16b8f022..f9223a6ffa 100644 --- a/apps/minifront/src/state/index.ts +++ b/apps/minifront/src/state/index.ts @@ -2,7 +2,7 @@ import { create, StateCreator } from 'zustand'; import { enableMapSet } from 'immer'; import { immer } from 'zustand/middleware/immer'; import { createSwapSlice, SwapSlice } from './swap'; -import { createIbcOutSlice, ibcOutMiddleware, IbcOutSlice } from './ibc-out'; +import { createIbcOutSlice, IbcOutSlice } from './ibc-out'; import { createSendSlice, sendSelectionMiddleware, SendSlice } from './send'; import { createStakingSlice, StakingSlice } from './staking'; import { createStatusSlice, StatusSlice } from './status'; @@ -51,20 +51,18 @@ export const initializeStore = () => { // middleware call. Thus, all other middlewares can't use immer's syntax in // their setters. return immer( - ibcOutMiddleware( - sendSelectionMiddleware( - swapBalancesMiddleware((setState, getState: () => AllSlices, store) => ({ - ibcIn: createIbcInSlice()(setState, getState, store), - ibcOut: createIbcOutSlice()(setState, getState, store), - send: createSendSlice()(setState, getState, store), - shared: createSharedSlice()(setState, getState, store), - staking: createStakingSlice()(setState, getState, store), - status: createStatusSlice()(setState, getState, store), - swap: createSwapSlice()(setState, getState, store), - transactions: createTransactionsSlice()(setState, getState, store), - unclaimedSwaps: createUnclaimedSwapsSlice()(setState, getState, store), - })), - ), + sendSelectionMiddleware( + swapBalancesMiddleware((setState, getState: () => AllSlices, store) => ({ + ibcIn: createIbcInSlice()(setState, getState, store), + ibcOut: createIbcOutSlice()(setState, getState, store), + send: createSendSlice()(setState, getState, store), + shared: createSharedSlice()(setState, getState, store), + staking: createStakingSlice()(setState, getState, store), + status: createStatusSlice()(setState, getState, store), + swap: createSwapSlice()(setState, getState, store), + transactions: createTransactionsSlice()(setState, getState, store), + unclaimedSwaps: createUnclaimedSwapsSlice()(setState, getState, store), + })), ), ); };