From fef7ff8d1db2328a2b8d6c60635fb77863f9715b Mon Sep 17 00:00:00 2001 From: Joseph Chalabi <100090645+chalabi2@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:39:01 -0700 Subject: [PATCH] fix: only use 1 source wallet | memoize components that call balances (#255) --- .github/workflows/build.yml | 15 ++ .../components/__tests__/sendBox.test.tsx | 2 - components/bank/components/sendBox.tsx | 30 +-- components/bank/components/tokenList.tsx | 23 +- .../bank/forms/__tests__/ibcSendForm.test.tsx | 72 +----- components/bank/forms/ibcSendForm.tsx | 241 ++++-------------- components/bank/modals/sendModal.tsx | 26 +- .../groups/components/groupControls.tsx | 1 - components/react/modal.tsx | 24 +- components/wallet.tsx | 48 +--- pages/bank.tsx | 102 +------- 11 files changed, 102 insertions(+), 482 deletions(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b1c1195 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,15 @@ +name: Build + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Bun + uses: oven-sh/setup-bun@v1 + - name: Install dependencies + run: bun install + - name: Build + run: bun run build diff --git a/components/bank/components/__tests__/sendBox.test.tsx b/components/bank/components/__tests__/sendBox.test.tsx index dc84a4b..da5788c 100644 --- a/components/bank/components/__tests__/sendBox.test.tsx +++ b/components/bank/components/__tests__/sendBox.test.tsx @@ -63,7 +63,6 @@ describe('SendBox', () => { // Verify cross-chain elements are present await waitFor(() => { - expect(screen.getByLabelText('from-chain-selector')).toBeInTheDocument(); expect(screen.getByLabelText('to-chain-selector')).toBeInTheDocument(); }); }); @@ -73,7 +72,6 @@ describe('SendBox', () => { fireEvent.click(screen.getByLabelText('cross-chain-transfer-tab')); await waitFor(() => { - expect(screen.getByLabelText('from-chain-selector')).toBeInTheDocument(); expect(screen.getByLabelText('to-chain-selector')).toBeInTheDocument(); }); }); diff --git a/components/bank/components/sendBox.tsx b/components/bank/components/sendBox.tsx index 155fe0d..48f6914 100644 --- a/components/bank/components/sendBox.tsx +++ b/components/bank/components/sendBox.tsx @@ -4,6 +4,7 @@ import IbcSendForm from '../forms/ibcSendForm'; import env from '@/config/env'; import { CombinedBalanceInfo } from '@/utils/types'; import { ChainContext } from '@cosmos-kit/core'; +import React from 'react'; export interface IbcChain { id: string; @@ -13,7 +14,7 @@ export interface IbcChain { chainID: string; } -export default function SendBox({ +export default React.memo(function SendBox({ address, balances, isBalancesLoading, @@ -23,11 +24,6 @@ export default function SendBox({ isGroup, admin, refetchProposals, - osmosisBalances, - isOsmosisBalancesLoading, - refetchOsmosisBalances, - resolveOsmosisRefetch, - chains, }: { address: string; balances: CombinedBalanceInfo[]; @@ -38,25 +34,20 @@ export default function SendBox({ selectedDenom?: string; isGroup?: boolean; admin?: string; - osmosisBalances: CombinedBalanceInfo[]; - isOsmosisBalancesLoading: boolean; - refetchOsmosisBalances: () => void; - resolveOsmosisRefetch: () => void; - chains: Record; }) { const ibcChains = useMemo( () => [ { id: env.chain, name: 'Manifest', - icon: 'logo.svg', + icon: '/logo.svg', prefix: 'manifest', chainID: env.chainId, }, { id: env.osmosisChain, name: 'Osmosis', - icon: 'osmosis.svg', + icon: '/osmosis.svg', prefix: 'osmo', chainID: env.osmosisChainId, }, @@ -74,6 +65,8 @@ export default function SendBox({ const [selectedFromChain, setSelectedFromChain] = useState(ibcChains[0]); const [selectedToChain, setSelectedToChain] = useState(ibcChains[1]); + const memoizedBalances = useMemo(() => balances, [balances]); + useEffect(() => { if (selectedFromChain && selectedToChain && selectedFromChain.id === selectedToChain.id) { // If chains match, switch the destination chain to the other available chain @@ -132,25 +125,20 @@ export default function SendBox({ setSelectedToChain={setSelectedToChain} address={address} destinationChain={selectedToChain} - balances={balances} + balances={memoizedBalances} isBalancesLoading={isBalancesLoading} refetchBalances={refetchBalances} refetchHistory={refetchHistory} selectedDenom={selectedDenom} - osmosisBalances={osmosisBalances} isGroup={isGroup} admin={admin} refetchProposals={refetchProposals} - isOsmosisBalancesLoading={isOsmosisBalancesLoading} - refetchOsmosisBalances={refetchOsmosisBalances} - resolveOsmosisRefetch={resolveOsmosisRefetch} availableToChains={getAvailableToChains} - chains={chains} /> ) : ( ); -} +}); diff --git a/components/bank/components/tokenList.tsx b/components/bank/components/tokenList.tsx index 9574029..6c42973 100644 --- a/components/bank/components/tokenList.tsx +++ b/components/bank/components/tokenList.tsx @@ -17,14 +17,9 @@ interface TokenListProps { admin?: string; refetchProposals?: () => void; searchTerm?: string; - osmosisBalances?: CombinedBalanceInfo[] | undefined; - isOsmosisBalancesLoading?: boolean; - refetchOsmosisBalances?: () => void; - resolveOsmosisRefetch?: () => void; - chains: Record; } -export function TokenList(props: Readonly) { +export const TokenList = React.memo(function TokenList(props: Readonly) { const { balances, isLoading, @@ -36,11 +31,6 @@ export function TokenList(props: Readonly) { admin, refetchProposals, searchTerm = '', - osmosisBalances, - isOsmosisBalancesLoading, - refetchOsmosisBalances, - resolveOsmosisRefetch, - chains, } = props; const [selectedDenom, setSelectedDenom] = useState(null); const [isSendModalOpen, setIsSendModalOpen] = useState(false); @@ -91,6 +81,8 @@ export function TokenList(props: Readonly) { [totalPages] ); + const memoizedBalances = useMemo(() => props.balances ?? [], [props.balances]); + return (
@@ -228,7 +220,7 @@ export function TokenList(props: Readonly) { modalId="send-modal" isOpen={isSendModalOpen} address={address} - balances={balances ?? []} + balances={memoizedBalances} isBalancesLoading={isLoading} refetchBalances={refetchBalances} refetchHistory={refetchHistory} @@ -237,12 +229,7 @@ export function TokenList(props: Readonly) { isGroup={isGroup} admin={admin} refetchProposals={refetchProposals} - osmosisBalances={osmosisBalances ?? []} - isOsmosisBalancesLoading={isOsmosisBalancesLoading ?? false} - refetchOsmosisBalances={refetchOsmosisBalances ?? (() => {})} - resolveOsmosisRefetch={resolveOsmosisRefetch ?? (() => {})} - chains={chains} />
); -} +}); diff --git a/components/bank/forms/__tests__/ibcSendForm.test.tsx b/components/bank/forms/__tests__/ibcSendForm.test.tsx index a5b3c46..d5091e4 100644 --- a/components/bank/forms/__tests__/ibcSendForm.test.tsx +++ b/components/bank/forms/__tests__/ibcSendForm.test.tsx @@ -16,6 +16,15 @@ mock.module('next/router', () => ({ }), })); +// Add this mock before the tests +mock.module('next/image', () => ({ + default: (props: any) => { + // eslint-disable-next-line @next/next/no-img-element + return {props.alt; + }, + __esModule: true, +})); + function renderWithProps(props = {}) { const defaultChains = [ { @@ -87,7 +96,6 @@ describe('IbcSendForm Component', () => { const { findForm } = renderWithProps(); const form = await findForm(); expect(form).toBeInTheDocument(); - expect(screen.getByLabelText('from-chain-selector')).toBeInTheDocument(); expect(screen.getByLabelText('to-chain-selector')).toBeInTheDocument(); }); @@ -114,25 +122,10 @@ describe('IbcSendForm Component', () => { test('updates chain selector correctly', () => { renderWithProps(); - - const fromChainSelector = screen.getByLabelText('from-chain-selector'); - fireEvent.click(fromChainSelector); - - // Get all Manifest options and select the enabled one - const manifestOptions = screen.getAllByRole('option', { name: 'Manifest' }); - const enabledManifestOption = manifestOptions.find( - option => !option.className.includes('opacity-50') - ); - fireEvent.click(enabledManifestOption!); - - expect(screen.getByLabelText('from-chain-selector')).toHaveTextContent('Manifest'); - const toChainSelector = screen.getByLabelText('to-chain-selector'); fireEvent.click(toChainSelector); - const osmosisOption = screen.getAllByRole('option', { name: 'Osmosis' }); fireEvent.click(osmosisOption[0]); - expect(screen.getByLabelText('to-chain-selector')).toHaveTextContent('Osmosis'); }); @@ -167,69 +160,28 @@ describe('IbcSendForm Component', () => { test('handles chain selection correctly', async () => { renderWithProps(); - - const fromChainSelector = screen.getByLabelText('from-chain-selector'); - fireEvent.click(fromChainSelector); - - const manifestOptions = screen.getAllByRole('option', { name: 'Manifest' }); - const enabledManifestOption = manifestOptions.find( - option => !option.className.includes('opacity-50') - ); - fireEvent.click(enabledManifestOption!); - - expect(screen.getByLabelText('from-chain-selector')).toHaveTextContent('Manifest'); - const toChainSelector = screen.getByLabelText('to-chain-selector'); fireEvent.click(toChainSelector); - const osmosisOption = screen.getAllByRole('option', { name: 'Osmosis' }); fireEvent.click(osmosisOption[0]); - expect(screen.getByLabelText('to-chain-selector')).toHaveTextContent('Osmosis'); }); - - test('prevents selecting same chain for source and destination', async () => { + // cant select from chain anymore hardcoded to manifest + test.skip('prevents selecting same chain for source and destination', async () => { const { findForm } = renderWithProps(); - await findForm(); // Wait for form to be mounted - - const fromChainSelector = screen.getByLabelText('from-chain-selector'); - await act(async () => { - fireEvent.click(fromChainSelector); - }); - - // Wait for dropdown content to be visible - await act(async () => { - const manifestOptions = await screen.findAllByRole('option', { - name: 'Manifest', - hidden: true, - }); - - const enabledManifestOption = manifestOptions.find( - option => !option.className.includes('opacity-50') - ); - fireEvent.click(enabledManifestOption!); - }); - - expect(screen.getByLabelText('from-chain-selector')).toHaveTextContent('Manifest'); - + await findForm(); const toChainSelector = screen.getByLabelText('to-chain-selector'); await act(async () => { fireEvent.click(toChainSelector); }); - - // Wait for dropdown content to be visible await act(async () => { const toChainOptions = await screen.findAllByRole('option', { hidden: true, }); - - // Find the Manifest option in the to-chain dropdown const manifestInToChain = toChainOptions.find(option => { const link = option.querySelector('a'); return link && link.textContent?.includes('Manifest'); }); - - // Check that the Manifest option has the disabled styling and attributes expect(manifestInToChain?.querySelector('a')).toHaveStyle({ pointerEvents: 'none' }); expect(manifestInToChain?.querySelector('a')).toHaveClass('opacity-50'); }); diff --git a/components/bank/forms/ibcSendForm.tsx b/components/bank/forms/ibcSendForm.tsx index b6e845e..1e20955 100644 --- a/components/bank/forms/ibcSendForm.tsx +++ b/components/bank/forms/ibcSendForm.tsx @@ -35,6 +35,7 @@ import { useSkipClient } from '@/contexts/skipGoContext'; import { IbcChain } from '@/components'; import { ChainContext } from '@cosmos-kit/core'; +import { useChain } from '@cosmos-kit/react'; //TODO: switch to main-net names export default function IbcSendForm({ @@ -52,14 +53,10 @@ export default function IbcSendForm({ setSelectedToChain, selectedDenom, isGroup, - osmosisBalances, - isOsmosisBalancesLoading, - refetchOsmosisBalances, - resolveOsmosisRefetch, + refetchProposals, admin, availableToChains, - chains, }: Readonly<{ address: string; destinationChain: IbcChain; @@ -75,39 +72,23 @@ export default function IbcSendForm({ selectedToChain: IbcChain; setSelectedToChain: (selectedChain: IbcChain) => void; selectedDenom?: string; - osmosisBalances: CombinedBalanceInfo[]; - isOsmosisBalancesLoading: boolean; - refetchOsmosisBalances: () => void; - resolveOsmosisRefetch: () => void; refetchProposals?: () => void; admin?: string; availableToChains: IbcChain[]; - chains: Record; }>) { - const formatTokenDisplayName = (displayName: string) => { - if (displayName.startsWith('factory')) { - return displayName.split('/').pop()?.toUpperCase(); - } - if (displayName.startsWith('u')) { - return displayName.slice(1).toUpperCase(); - } - return truncateString(displayName, 10).toUpperCase(); - }; - const [isSending, setIsSending] = useState(false); const [searchTerm, setSearchTerm] = useState(''); const [feeWarning, setFeeWarning] = useState(''); - const { tx } = useTx(selectedFromChain.name === env.osmosisChain ? env.osmosisChain : env.chain); - const { estimateFee } = useFeeEstimation( - selectedFromChain.name === env.osmosisChain ? env.osmosisChain : env.chain - ); + const { getOfflineSignerAmino } = useChain(env.chain); + const { tx } = useTx(env.chain); + const { estimateFee } = useFeeEstimation(env.chain); const { transfer } = ibc.applications.transfer.v1.MessageComposer.withTypeUrl; const [isContactsOpen, setIsContactsOpen] = useState(false); const [isIconRotated, setIsIconRotated] = useState(false); - + console.log(balances); const getCosmosSigner = async () => { - const signer = chains[selectedFromChain.id].getOfflineSignerAmino(); + const signer = getOfflineSignerAmino(); return signer; }; @@ -126,30 +107,19 @@ export default function IbcSendForm({ // Update the filtered balances logic to use passed props instead of hooks const filteredBalances = useMemo(() => { - const sourceBalances = selectedFromChain.id === env.osmosisChain ? osmosisBalances : balances; - - return sourceBalances?.filter(token => { + return balances?.filter(token => { const displayName = token.metadata?.display ?? token.denom; return displayName.toLowerCase().includes(searchTerm.toLowerCase()); }); - }, [balances, osmosisBalances, searchTerm, selectedFromChain]); + }, [balances, searchTerm, selectedFromChain]); // Update initialSelectedToken to consider the chain const initialSelectedToken = useMemo(() => { - const sourceBalances = selectedFromChain.id === env.osmosisChain ? osmosisBalances : balances; - - return ( - sourceBalances?.find(token => token.coreDenom === selectedDenom) || - sourceBalances?.[0] || - null - ); - }, [balances, osmosisBalances, selectedDenom, selectedFromChain]); + return balances?.find(token => token.coreDenom === selectedDenom) || balances?.[0] || null; + }, [balances, selectedDenom, selectedFromChain]); // Update the loading check - if ( - (selectedFromChain.id === env.osmosisChain ? isOsmosisBalancesLoading : isBalancesLoading) || - !initialSelectedToken - ) { + if (isBalancesLoading || !initialSelectedToken) { return null; } @@ -225,25 +195,16 @@ export default function IbcSendForm({ console.log('route', route); console.log('route.requiredChainAddresses', route.requiredChainAddresses); - const addressList = route.requiredChainAddresses.map((chainID: string) => ({ - address: - Object.values(chains).find(chain => chain.chain.chain_id === chainID)?.address ?? '', - })); - - const userAddresses = route.requiredChainAddresses.map((chainID: string) => { - const chainContext = Object.values(chains).find(chain => chain.chain.chain_id === chainID); - - if (!chainContext?.address) { - throw new Error(`No address found for chain: ${chainID}`); - } - - return { - chainID, - address: chainContext.address, - }; - }); - - console.log(userAddresses); + const userAddresses = [ + { + chainID: selectedFromChain.chainID, + address: address, + }, + { + chainID: selectedToChain.chainID, + address: values.recipient, + }, + ]; const messages = await skipClient.messages({ sourceAssetDenom: values.selectedToken.coreDenom, @@ -252,7 +213,7 @@ export default function IbcSendForm({ destAssetChainID: selectedToChain.chainID, amountIn: amountInBaseUnits, amountOut: route.estimatedAmountOut ?? '', - addressList: addressList.map((user: { address: any }) => user.address), + addressList: userAddresses.map((user: { address: any }) => user.address), operations: route.operations, estimatedAmountOut: route.estimatedAmountOut ?? '', slippageTolerancePercent: '1', @@ -376,143 +337,31 @@ export default function IbcSendForm({
{/* From Chain */}
- {isGroup ? ( - // Static display for groups -
- - From Chain - -
- - chain.id === env.chain)?.icon || ''} - alt={ibcChains.find(chain => chain.id === env.chain)?.name || ''} - width={24} - height={24} - className="mr-2" - /> - - {ibcChains.find(chain => chain.id === env.chain)?.name} - - -
-
- ) : ( - // Dropdown for non-group transfers -
-
- - From Chain +
+ + From Chain + +
+ + chain.id === env.chain)?.icon || ''} + alt={ibcChains.find(chain => chain.id === env.chain)?.name || ''} + width={24} + height={24} + className="mr-2" + /> + + {ibcChains.find(chain => chain.id === env.chain)?.name} - -
- - +
- )} +
{/* Switch Button */} -
{ e.preventDefault(); setIsIconRotated(!isIconRotated); @@ -530,7 +379,7 @@ export default function IbcSendForm({ }`} /> -
+
*/} {/* To Chain (Osmosis) */}
void; - osmosisBalances: CombinedBalanceInfo[]; - isOsmosisBalancesLoading: boolean; - refetchOsmosisBalances: () => void; - resolveOsmosisRefetch: () => void; - chains: Record; } -export default function SendModal({ +export default React.memo(function SendModal({ modalId, address, balances, @@ -38,11 +32,6 @@ export default function SendModal({ isGroup, admin, refetchProposals, - osmosisBalances, - isOsmosisBalancesLoading, - refetchOsmosisBalances, - resolveOsmosisRefetch, - chains, }: SendModalProps) { const handleClose = () => { if (setOpen) { @@ -62,6 +51,8 @@ export default function SendModal({ return () => document.removeEventListener('keydown', handleEscape); }, [isOpen]); + const memoizedBalances = useMemo(() => balances, [balances]); + const modalContent = (
)}
diff --git a/components/react/modal.tsx b/components/react/modal.tsx index fe4b61c..9b416e8 100644 --- a/components/react/modal.tsx +++ b/components/react/modal.tsx @@ -36,8 +36,6 @@ import { Web3AuthClient, Web3AuthWallet } from '@cosmos-kit/web3auth'; import { useDeviceDetect } from '@/hooks'; import { State } from '@cosmos-kit/core'; import { ExpiredError } from '@cosmos-kit/core'; -import env from '@/config/env'; -import { useChains } from '@cosmos-kit/react'; export enum ModalView { WalletList, @@ -94,25 +92,7 @@ export const TailwindModal: React.FC< const [qrState, setQRState] = useState(State.Init); const [qrMessage, setQrMessage] = useState(''); - const chains = useChains([env.chain, env.osmosisChain, env.axelarChain]); - - const chainStates = useMemo(() => { - return Object.values(chains).map(chain => ({ - connect: chain.connect, - openView: chain.openView, - status: chain.status, - username: chain.username, - address: chain.address, - disconnect: chain.disconnect, - })); - }, [chains]); - - const disconnect = async () => { - await Promise.all(chainStates.map(chain => chain.disconnect())); - }; - - const current = chains?.manifesttestnet?.walletRepo?.current; - + const current = walletRepo?.current; const currentWalletData = current?.walletInfo; const walletStatus = current?.walletStatus || WalletStatus.Disconnected; const currentWalletName = current?.walletName; @@ -464,7 +444,7 @@ export const TailwindModal: React.FC< setCurrentView(ModalView.WalletList)} - disconnect={() => disconnect()} + disconnect={() => current?.disconnect()} name={currentWalletData?.prettyName!} logo={currentWalletData?.logo!.toString() ?? ''} username={current?.username} diff --git a/components/wallet.tsx b/components/wallet.tsx index fee320f..54db3e1 100644 --- a/components/wallet.tsx +++ b/components/wallet.tsx @@ -2,7 +2,7 @@ import React, { MouseEventHandler, useEffect, useMemo, useState, useRef } from ' import { ArrowDownTrayIcon, ArrowPathIcon } from '@heroicons/react/24/outline'; import { ArrowUpIcon, CopyIcon } from './icons'; -import { useChain, useChains } from '@cosmos-kit/react'; +import { useChain } from '@cosmos-kit/react'; import { WalletStatus } from 'cosmos-kit'; import { MdWallet } from 'react-icons/md'; import env from '@/config/env'; @@ -37,51 +37,7 @@ interface WalletSectionProps { } export const WalletSection: React.FC = ({ chainName }) => { - const chains = useChains([env.chain, env.osmosisChain, env.axelarChain]); - - const chainStates = useMemo(() => { - return Object.values(chains).map(chain => ({ - connect: chain.connect, - openView: chain.openView, - status: chain.status, - username: chain.username, - address: chain.address, - wallet: chain.wallet, - })); - }, [chains]); - - const connect = async () => { - await Promise.all(chainStates.map(chain => chain.connect())); - }; - - const openView = () => { - chainStates[0]?.openView(); - }; - - const status = useMemo(() => { - if (chainStates.some(chain => chain.status === WalletStatus.Connecting)) { - return WalletStatus.Connecting; - } - if (chainStates.some(chain => chain.status === WalletStatus.Error)) { - return WalletStatus.Error; - } - if (chainStates.every(chain => chain.status === WalletStatus.Connected)) { - return WalletStatus.Connected; - } - return WalletStatus.Disconnected; - }, [chainStates]); - - const username = useMemo( - () => chainStates.find(chain => chain.username)?.username || undefined, - [chainStates] - ); - - const wallet = useMemo(() => chainStates.find(chain => chain.wallet)?.wallet, [chainStates]); - - const address = useMemo( - () => chainStates.find(chain => chain.address)?.address || undefined, - [chainStates] - ); + const { connect, openView, status, username, address, wallet } = useChain(chainName); const [localStatus, setLocalStatus] = useState(status); const timeoutRef = useRef>(); diff --git a/pages/bank.tsx b/pages/bank.tsx index cbf6d97..e142d04 100644 --- a/pages/bank.tsx +++ b/pages/bank.tsx @@ -29,21 +29,14 @@ interface PageSizeConfig { } export default function Bank() { - const chains = useChains([env.chain, env.osmosisChain, env.axelarChain]); + const { isWalletConnected, address } = useChain(env.chain); - const isWalletConnected = useMemo( - () => Object.values(chains).every(chain => chain.isWalletConnected), - [chains] - ); - - const { balances, isBalancesLoading, refetchBalances } = useTokenBalances( - chains.manifesttestnet.address ?? '' - ); + const { balances, isBalancesLoading, refetchBalances } = useTokenBalances(address ?? ''); const { balances: resolvedBalances, isBalancesLoading: resolvedLoading, refetchBalances: resolveRefetch, - } = useTokenBalancesResolved(chains.manifesttestnet.address ?? ''); + } = useTokenBalancesResolved(address ?? ''); const { metadatas, isMetadatasLoading } = useTokenFactoryDenomsMetadata(); const [currentPage, setCurrentPage] = useState(1); @@ -93,12 +86,7 @@ export default function Bank() { isError, refetch: refetchHistory, totalCount, - } = useGetMessagesFromAddress( - env.indexerUrl, - chains.manifesttestnet.address ?? '', - currentPage, - historyPageSize - ); + } = useGetMessagesFromAddress(env.indexerUrl, address ?? '', currentPage, historyPageSize); const combinedBalances = useMemo(() => { if (!balances || !resolvedBalances || !metadatas) return []; @@ -164,79 +152,6 @@ export default function Bank() { return mfxCombinedBalance ? [mfxCombinedBalance, ...otherBalances] : otherBalances; }, [balances, resolvedBalances, metadatas]); - const { - balances: osmosisBalances, - isBalancesLoading: isOsmosisBalancesLoading, - refetchBalances: refetchOsmosisBalances, - } = useTokenBalancesOsmosis(chains.osmosistestnet.address ?? ''); - const { - balances: resolvedOsmosisBalances, - isBalancesLoading: resolvedOsmosisLoading, - refetchBalances: resolveOsmosisRefetch, - } = useOsmosisTokenBalancesResolved(chains.osmosistestnet.address ?? ''); - - const { - metadatas: osmosisMetadatas, - isMetadatasLoading: isOsmosisMetadatasLoading, - refetchMetadatas: refetchOsmosisMetadatas, - } = useOsmosisTokenFactoryDenomsMetadata(); - - const combinedOsmosisBalances = useMemo(() => { - if (!osmosisBalances || !resolvedOsmosisBalances || !osmosisMetadatas) { - return []; - } - - const combined = osmosisBalances.map((coreBalance): CombinedBalanceInfo => { - // Handle OSMO token specifically - if (coreBalance.denom === 'uosmo') { - return { - denom: 'uosmo', - coreDenom: coreBalance.denom, - amount: coreBalance.amount, - metadata: OSMOSIS_TOKEN_DATA, - }; - } - - // Handle IBC tokens - if (coreBalance.denom.startsWith('ibc/')) { - const assetInfo = denomToAsset(env.osmosisChain, coreBalance.denom); - - const baseDenom = assetInfo?.traces?.[1]?.counterparty?.base_denom; - - return { - denom: baseDenom ?? '', // normalized denom (e.g., 'umfx') - coreDenom: coreBalance.denom, // full IBC trace - amount: coreBalance.amount, - metadata: { - description: assetInfo?.description ?? '', - denom_units: - assetInfo?.denom_units?.map(unit => ({ - ...unit, - aliases: unit.aliases || [], - })) ?? [], - base: assetInfo?.base ?? '', - display: assetInfo?.display ?? '', - name: assetInfo?.name ?? '', - symbol: assetInfo?.symbol ?? '', - uri: assetInfo?.logo_URIs?.svg ?? assetInfo?.logo_URIs?.png ?? '', - uri_hash: assetInfo?.logo_URIs?.svg ?? assetInfo?.logo_URIs?.png ?? '', - }, - }; - } - - // Handle all other tokens - const metadata = osmosisMetadatas.metadatas?.find(m => m.base === coreBalance.denom); - return { - denom: coreBalance.denom, - coreDenom: coreBalance.denom, - amount: coreBalance.amount, - metadata: metadata || null, - }; - }); - - return combined; - }, [osmosisBalances, resolvedOsmosisBalances, osmosisMetadatas]); - const isLoading = isBalancesLoading || resolvedLoading || isMetadatasLoading; const [searchTerm, setSearchTerm] = useState(''); @@ -309,17 +224,12 @@ export default function Bank() { ) : ( ))} {activeTab === 'history' && @@ -329,7 +239,7 @@ export default function Bank() {