@@ -127,6 +124,7 @@ export const AssetSelector = ({ assets, onChange, value, filter }: AssetSelector
icon={
}
value={search}
onChange={setSearch}
+ autoFocus
placeholder='Search assets...'
/>
diff --git a/apps/minifront/src/components/shared/selectors/balance-item.tsx b/apps/minifront/src/components/shared/selectors/balance-item.tsx
new file mode 100644
index 0000000000..0aad0c58a2
--- /dev/null
+++ b/apps/minifront/src/components/shared/selectors/balance-item.tsx
@@ -0,0 +1,58 @@
+import { BalanceOrMetadata, isBalance, isMetadata } from './helpers';
+import { getAddressIndex } from '@penumbra-zone/getters/address-view';
+import { getMetadataFromBalancesResponseOptional } from '@penumbra-zone/getters/balances-response';
+import { useMemo } from 'react';
+import { DialogClose } from '@repo/ui/components/ui/dialog';
+import { cn } from '@repo/ui/lib/utils';
+import { AssetIcon } from '@repo/ui/components/ui/tx/view/asset-icon';
+import { ValueViewComponent } from '@repo/ui/components/ui/tx/view/value';
+
+interface BalanceItemProps {
+ asset: BalanceOrMetadata;
+ value?: BalanceOrMetadata;
+ onSelect: (value: BalanceOrMetadata) => void;
+}
+
+export const BalanceItem = ({ asset, value, onSelect }: BalanceItemProps) => {
+ const account = isBalance(asset) ? getAddressIndex(asset.accountAddress).account : undefined;
+ const metadata = isMetadata(asset) ? asset : getMetadataFromBalancesResponseOptional(asset);
+
+ const isSelected = useMemo(() => {
+ if (!value) return false;
+ if (isMetadata(value) && isMetadata(asset)) {
+ return value.equals(asset);
+ }
+ if (isBalance(value) && isBalance(asset)) {
+ return value.equals(asset);
+ }
+ return false;
+ }, [asset, value]);
+
+ return (
+
+
onSelect(asset)}>
+
+ {metadata && (
+
+
+
{metadata.symbol || 'Unknown asset'}
+
+ )}
+
+
+ {isBalance(asset) && (
+
+ )}
+
+
+
{account}
+
+
+
+ );
+};
diff --git a/apps/minifront/src/components/shared/selectors/balance-selector.tsx b/apps/minifront/src/components/shared/selectors/balance-selector.tsx
new file mode 100644
index 0000000000..c2248775be
--- /dev/null
+++ b/apps/minifront/src/components/shared/selectors/balance-selector.tsx
@@ -0,0 +1,103 @@
+import { MagnifyingGlassIcon } from '@radix-ui/react-icons';
+import { useId, useState } from 'react';
+import { IconInput } from '@repo/ui/components/ui/icon-input';
+import { Dialog, DialogContent, DialogHeader } from '@repo/ui/components/ui/dialog';
+import { ValueViewComponent } from '@repo/ui/components/ui/tx/view/value';
+import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';
+import { Box } from '@repo/ui/components/ui/box';
+import { motion } from 'framer-motion';
+import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb';
+import { emptyBalanceResponse } from '../../../utils/empty-balance-response';
+import { bySearch } from './search-filters';
+import { BalanceOrMetadata, isMetadata, mergeBalancesAndAssets } from './helpers';
+import { BalanceItem } from './balance-item';
+
+interface BalanceSelectorProps {
+ value: BalancesResponse | undefined;
+ onChange: (selection: BalancesResponse) => void;
+ balances?: BalancesResponse[];
+ assets?: Metadata[];
+}
+
+/**
+ * Renders balances the user holds, and allows the user to select one. This is
+ * useful for a form where the user wants to send/sell/swap an asset that they
+ * already hold.
+ *
+ * Use `
` if you want to render assets that aren't tied to any
+ * balance.
+ */
+export default function BalanceSelector({
+ value,
+ balances,
+ onChange,
+ assets,
+}: BalanceSelectorProps) {
+ const [search, setSearch] = useState('');
+ const [isOpen, setIsOpen] = useState(false);
+ const layoutId = useId();
+
+ const allAssets = mergeBalancesAndAssets(balances, assets);
+ const filteredBalances = search ? allAssets.filter(bySearch(search)) : allAssets;
+
+ const onSelect = (asset: BalanceOrMetadata) => {
+ if (!isMetadata(asset)) {
+ onChange(asset);
+ return;
+ }
+ onChange(emptyBalanceResponse(asset));
+ };
+
+ return (
+ <>
+ {!isOpen && (
+
setIsOpen(true)}
+ >
+
+
+ )}
+
+ {isOpen && (
+ <>
+ {/* 0-opacity placeholder for layout's sake */}
+
+
+
+ >
+ )}
+
+
+ >
+ );
+}
diff --git a/apps/minifront/src/components/shared/selectors/helpers.ts b/apps/minifront/src/components/shared/selectors/helpers.ts
new file mode 100644
index 0000000000..b11fb88f6c
--- /dev/null
+++ b/apps/minifront/src/components/shared/selectors/helpers.ts
@@ -0,0 +1,26 @@
+import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';
+import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb';
+import { getMetadataFromBalancesResponseOptional } from '@penumbra-zone/getters/balances-response';
+
+export type BalanceOrMetadata = BalancesResponse | Metadata;
+
+export const isMetadata = (asset: BalancesResponse | Metadata): asset is Metadata => {
+ return asset.getType().typeName === Metadata.typeName;
+};
+
+export const isBalance = (asset: BalancesResponse | Metadata): asset is BalancesResponse => {
+ return asset.getType().typeName === BalancesResponse.typeName;
+};
+
+export const mergeBalancesAndAssets = (
+ balances: BalancesResponse[] = [],
+ assets: Metadata[] = [],
+): BalanceOrMetadata[] => {
+ const filteredAssets = assets.filter(asset => {
+ return !balances.some(balance => {
+ const balanceMetadata = getMetadataFromBalancesResponseOptional(balance);
+ return balanceMetadata?.equals(asset);
+ });
+ });
+ return [...balances, ...filteredAssets];
+};
diff --git a/apps/minifront/src/components/shared/selectors/search-filters.ts b/apps/minifront/src/components/shared/selectors/search-filters.ts
new file mode 100644
index 0000000000..73993de6ce
--- /dev/null
+++ b/apps/minifront/src/components/shared/selectors/search-filters.ts
@@ -0,0 +1,32 @@
+import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';
+import { isKnown } from '../../swap/helpers';
+import { getDisplayDenomFromView, getSymbolFromValueView } from '@penumbra-zone/getters/value-view';
+import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb';
+import { type BalanceOrMetadata, isBalance, isMetadata } from './helpers';
+
+export const balanceBySearch =
+ (search: string) =>
+ (balancesResponse: BalancesResponse): boolean =>
+ isKnown(balancesResponse) &&
+ (getDisplayDenomFromView(balancesResponse.balanceView)
+ .toLocaleLowerCase()
+ .includes(search.toLocaleLowerCase()) ||
+ getSymbolFromValueView(balancesResponse.balanceView)
+ .toLocaleLowerCase()
+ .includes(search.toLocaleLowerCase()));
+
+export const metadataBySearch =
+ (search: string) =>
+ (asset: Metadata): boolean =>
+ asset.display.toLocaleLowerCase().includes(search.toLocaleLowerCase()) ||
+ asset.symbol.toLocaleLowerCase().includes(search.toLocaleLowerCase());
+
+export const bySearch =
+ (search: string) =>
+ (asset: BalanceOrMetadata): boolean => {
+ if (isMetadata(asset)) {
+ return metadataBySearch(search)(asset);
+ }
+ if (isBalance(asset)) return balanceBySearch(search)(asset);
+ return false;
+ };
diff --git a/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx b/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx
index 18116e4ff1..8ea734d756 100644
--- a/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx
+++ b/apps/minifront/src/components/swap/swap-form/token-swap-input.tsx
@@ -1,5 +1,6 @@
-import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';
import { BalanceValueView } from '@repo/ui/components/ui/balance-value-view';
+import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';
+import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb';
import { Box } from '@repo/ui/components/ui/box';
import { CandlestickPlot } from '@repo/ui/components/ui/candlestick-plot';
import { Input } from '@repo/ui/components/ui/input';
@@ -13,41 +14,25 @@ import { ArrowRight } from 'lucide-react';
import { useEffect, useState } from 'react';
import { getBlockDate } from '../../../fetchers/block-date';
import { AllSlices } from '../../../state';
-import { amountMoreThanBalance } from '../../../state/send';
import { useStoreShallow } from '../../../utils/use-store-shallow';
import { getFormattedAmtFromValueView } from '@penumbra-zone/types/value-view';
import { getAddressIndex } from '@penumbra-zone/getters/address-view';
-import {
- Metadata,
- ValueView,
-} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb';
-import { AssetSelector } from '../../shared/asset-selector';
-import BalanceSelector from '../../shared/balance-selector';
-import { Amount } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/num/v1/num_pb';
+import { AssetSelector } from '../../shared/selectors/asset-selector';
+import BalanceSelector from '../../shared/selectors/balance-selector';
import { useStatus } from '../../../state/status';
import { hasStakingToken } from '../../../fetchers/staking-token';
import { useStakingTokenMetadata } from '../../../state/shared';
import { useBalancesResponses, useSwappableAssets } from '../../../state/swap';
import { FadeIn } from '@repo/ui/components/ui/fade-in';
-
-const isValidAmount = (amount: string, assetIn?: BalancesResponse) =>
- Number(amount) >= 0 && (!assetIn || !amountMoreThanBalance(assetIn, amount));
-
-const getKnownZeroValueView = (metadata?: Metadata) => {
- return new ValueView({
- valueView: {
- case: 'knownAssetId',
- value: { amount: new Amount({ lo: 0n }), metadata },
- },
- });
-};
+import { zeroValueView } from '../../../utils/zero-value-view';
+import { isValidAmount } from '../../../state/helpers';
const getAssetOutBalance = (
balancesResponses: BalancesResponse[] = [],
assetIn?: BalancesResponse,
assetOut?: Metadata,
) => {
- if (!assetIn || !assetOut) return getKnownZeroValueView();
+ if (!assetIn || !assetOut) return zeroValueView();
const match = balancesResponses.find(balance => {
const balanceViewMetadata = getMetadataFromBalancesResponse(balance);
@@ -57,7 +42,7 @@ const getAssetOutBalance = (
);
});
const matchedBalance = getBalanceView.optional()(match);
- return matchedBalance ?? getKnownZeroValueView(assetOut);
+ return matchedBalance ?? zeroValueView(assetOut);
};
const tokenSwapInputSelector = (state: AllSlices) => ({
@@ -124,7 +109,6 @@ export const TokenSwapInput = () => {
step='any'
className={'font-bold leading-10 md:h-8 md:text-xl xl:h-10 xl:text-3xl'}
onChange={e => {
- if (!isValidAmount(e.target.value, assetIn)) return;
setAmount(e.target.value);
setShowNonNativeFeeWarning(Number(e.target.value) > 0 && !userHasStakingToken);
}}
@@ -151,12 +135,14 @@ export const TokenSwapInput = () => {
{balancesResponses.data && (
)}
{assetIn?.balanceView && (
diff --git a/apps/minifront/src/state/helpers.ts b/apps/minifront/src/state/helpers.ts
index ed36b76bab..47353785d6 100644
--- a/apps/minifront/src/state/helpers.ts
+++ b/apps/minifront/src/state/helpers.ts
@@ -1,6 +1,7 @@
import {
AuthorizeAndBuildRequest,
AuthorizeAndBuildResponse,
+ BalancesResponse,
BroadcastTransactionRequest,
BroadcastTransactionResponse,
TransactionPlannerRequest,
@@ -18,6 +19,8 @@ import { PartialMessage } from '@bufbuild/protobuf';
import { TransactionToast } from '@repo/ui/lib/toast/transaction-toast';
import { TransactionClassification } from '@penumbra-zone/perspective/transaction/classification';
import { uint8ArrayToHex } from '@penumbra-zone/types/hex';
+import { fromValueView } from '@penumbra-zone/types/amount';
+import { BigNumber } from 'bignumber.js';
/**
* Handles the common use case of planning, building, and broadcasting a
@@ -156,3 +159,22 @@ export const userDeniedTransaction = (e: unknown): boolean =>
export const unauthenticated = (e: unknown): boolean =>
e instanceof Error && e.message.startsWith('[unauthenticated]');
+
+export const amountMoreThanBalance = (
+ asset: BalancesResponse,
+ /**
+ * The amount that a user types into the interface will always be in the
+ * display denomination -- e.g., in `penumbra`, not in `upenumbra`.
+ */
+ amountInDisplayDenom: string,
+): boolean => {
+ if (!asset.balanceView) {
+ throw new Error('Missing balanceView');
+ }
+
+ const balanceAmt = fromValueView(asset.balanceView);
+ return Boolean(amountInDisplayDenom) && BigNumber(amountInDisplayDenom).gt(balanceAmt);
+};
+
+export const isValidAmount = (amount: string, assetIn?: BalancesResponse) =>
+ Number(amount) >= 0 && (!assetIn || !amountMoreThanBalance(assetIn, amount));
diff --git a/apps/minifront/src/state/ibc-out.ts b/apps/minifront/src/state/ibc-out.ts
index 13c801d4d6..0d25600abd 100644
--- a/apps/minifront/src/state/ibc-out.ts
+++ b/apps/minifront/src/state/ibc-out.ts
@@ -14,8 +14,7 @@ import {
} from '@penumbra-zone/getters/value-view';
import { getAddressIndex } from '@penumbra-zone/getters/address-view';
import { toBaseUnit } from '@penumbra-zone/types/lo-hi';
-import { planBuildBroadcast } from './helpers';
-import { amountMoreThanBalance } from './send';
+import { amountMoreThanBalance, planBuildBroadcast } from './helpers';
import { getAssetId } from '@penumbra-zone/getters/metadata';
import { assetPatterns } from '@penumbra-zone/types/assets';
import { bech32, bech32m } from 'bech32';
diff --git a/apps/minifront/src/state/send.ts b/apps/minifront/src/state/send.ts
index 0868ec81e0..ce936ef0c6 100644
--- a/apps/minifront/src/state/send.ts
+++ b/apps/minifront/src/state/send.ts
@@ -6,7 +6,7 @@ import {
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';
import { BigNumber } from 'bignumber.js';
import { MemoPlaintext } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb';
-import { plan, planBuildBroadcast } from './helpers';
+import { amountMoreThanBalance, plan, planBuildBroadcast } from './helpers';
import {
Fee,
@@ -18,7 +18,6 @@ import {
} from '@penumbra-zone/getters/value-view';
import { getAddress, getAddressIndex } from '@penumbra-zone/getters/address-view';
import { toBaseUnit } from '@penumbra-zone/types/lo-hi';
-import { fromValueView } from '@penumbra-zone/types/amount';
import { isAddress } from '@penumbra-zone/bech32m/penumbra';
import { ZQueryState, createZQuery } from '@penumbra-zone/zquery';
import { getTransferableBalancesResponses } from '../components/send/helpers';
@@ -171,22 +170,6 @@ const assembleRequest = ({ amount, feeTier, recipient, selection, memo }: SendSl
});
};
-export const amountMoreThanBalance = (
- asset: BalancesResponse,
- /**
- * The amount that a user types into the interface will always be in the
- * display denomination -- e.g., in `penumbra`, not in `upenumbra`.
- */
- amountInDisplayDenom: string,
-): boolean => {
- if (!asset.balanceView) {
- throw new Error('Missing balanceView');
- }
-
- const balanceAmt = fromValueView(asset.balanceView);
- return Boolean(amountInDisplayDenom) && BigNumber(amountInDisplayDenom).gt(balanceAmt);
-};
-
export interface SendValidationFields {
recipientErr: boolean;
amountErr: boolean;
diff --git a/apps/minifront/src/state/swap/index.test.ts b/apps/minifront/src/state/swap/index.test.ts
index 9ac1d94ea9..4bf06bc381 100644
--- a/apps/minifront/src/state/swap/index.test.ts
+++ b/apps/minifront/src/state/swap/index.test.ts
@@ -66,7 +66,7 @@ describe('Swap Slice', () => {
test('assetOut can be set', () => {
expect(useStore.getState().swap.assetOut).toBeUndefined();
- useStore.getState().swap.setAssetOut(registryAssets[0]!);
+ useStore.getState().swap.setAssetOut(registryAssets[0]);
expect(useStore.getState().swap.assetOut).toBe(registryAssets[0]);
});
diff --git a/apps/minifront/src/state/swap/index.ts b/apps/minifront/src/state/swap/index.ts
index a09f862f93..10af3701f8 100644
--- a/apps/minifront/src/state/swap/index.ts
+++ b/apps/minifront/src/state/swap/index.ts
@@ -7,32 +7,42 @@ import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumb
import { AllSlices, SliceCreator, useStore } from '..';
import { DurationOption } from './constants';
import {
- DutchAuctionSlice,
createDutchAuctionSlice,
+ DutchAuctionSlice,
dutchAuctionSubmitButtonDisabledSelector,
} from './dutch-auction';
import {
- InstantSwapSlice,
createInstantSwapSlice,
+ InstantSwapSlice,
instantSwapSubmitButtonDisabledSelector,
} from './instant-swap';
-import { PriceHistorySlice, createPriceHistorySlice } from './price-history';
+import { createPriceHistorySlice, PriceHistorySlice } from './price-history';
import { getMetadata } from '@penumbra-zone/getters/value-view';
-import { ZQueryState, createZQuery } from '@penumbra-zone/zquery';
+import { createZQuery, ZQueryState } from '@penumbra-zone/zquery';
import { getSwappableBalancesResponses, isSwappable } from '../../components/swap/helpers';
import { getAllAssets } from '../../fetchers/assets';
+import { emptyBalanceResponse } from '../../utils/empty-balance-response';
+import { isValidAmount } from '../helpers';
+
+// When both `balancesResponses` and `swappableAssets` are loaded, set initial assetIn and assetOut
+const setInitialAssets = (state: SwapSlice) => {
+ if (state.swappableAssets.loading || state.balancesResponses.loading) return;
+
+ const firstBalancesResponse = state.balancesResponses.data?.[0];
+ const firstMetadata = state.swappableAssets.data?.[0];
+ const secondMetadata = state.swappableAssets.data?.[0];
+ if (firstBalancesResponse) {
+ state.setAssetIn(firstBalancesResponse);
+ state.setAssetOut(firstMetadata);
+ } else if (firstMetadata) {
+ state.setAssetIn(emptyBalanceResponse(firstMetadata));
+ state.setAssetOut(secondMetadata);
+ }
+};
export const { balancesResponses, useBalancesResponses } = createZQuery({
name: 'balancesResponses',
- fetch: async () => {
- const balancesResponses = await getSwappableBalancesResponses();
-
- if (balancesResponses[0] && !useStore.getState().swap.assetIn) {
- useStore.getState().swap.setAssetIn(balancesResponses[0]);
- }
-
- return balancesResponses;
- },
+ fetch: () => getSwappableBalancesResponses(),
getUseStore: () => useStore,
get: state => state.swap.balancesResponses,
set: setter => {
@@ -40,6 +50,7 @@ export const { balancesResponses, useBalancesResponses } = createZQuery({
useStore.setState(state => {
state.swap.balancesResponses = newState;
});
+ setInitialAssets(useStore.getState().swap);
},
});
@@ -47,13 +58,7 @@ export const { swappableAssets, useSwappableAssets } = createZQuery({
name: 'swappableAssets',
fetch: async () => {
const allAssets = await getAllAssets();
- const swappableAssets = allAssets.filter(isSwappable);
-
- if (swappableAssets[0] && !useStore.getState().swap.assetOut) {
- useStore.getState().swap.setAssetOut(swappableAssets[0]);
- }
-
- return swappableAssets;
+ return allAssets.filter(isSwappable);
},
getUseStore: () => useStore,
get: state => state.swap.swappableAssets,
@@ -62,6 +67,7 @@ export const { swappableAssets, useSwappableAssets } = createZQuery({
useStore.setState(state => {
state.swap.swappableAssets = newState;
});
+ setInitialAssets(useStore.getState().swap);
},
});
@@ -76,7 +82,7 @@ export interface SimulateSwapResult {
interface Actions {
setAssetIn: (asset: BalancesResponse) => void;
setAmount: (amount: string) => void;
- setAssetOut: (metadata: Metadata) => void;
+ setAssetOut: (metadata?: Metadata) => void;
setDuration: (duration: DurationOption) => void;
resetSubslices: VoidFunction;
}
@@ -177,5 +183,6 @@ export const createSwapSlice = (): SliceCreator
=> (set, get, store)
export const submitButtonDisabledSelector = (state: AllSlices) =>
!state.swap.amount ||
+ !isValidAmount(state.swap.amount, state.swap.assetIn) ||
dutchAuctionSubmitButtonDisabledSelector(state) ||
instantSwapSubmitButtonDisabledSelector(state);
diff --git a/apps/minifront/src/state/swap/instant-swap.ts b/apps/minifront/src/state/swap/instant-swap.ts
index f37bf96864..394702b869 100644
--- a/apps/minifront/src/state/swap/instant-swap.ts
+++ b/apps/minifront/src/state/swap/instant-swap.ts
@@ -1,6 +1,6 @@
import { AllSlices, SliceCreator } from '..';
import { TransactionPlannerRequest } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';
-import { planBuildBroadcast } from '../helpers';
+import { isValidAmount, planBuildBroadcast } from '../helpers';
import {
AssetId,
Metadata,
@@ -168,7 +168,9 @@ const assembleSwapRequest = async ({
amount,
assetOut,
}: Pick) => {
- if (!assetIn) throw new Error('`assetIn` was undefined');
+ if (!assetIn) throw new Error('`assetIn` is undefined');
+ if (!assetOut) throw new Error('`assetOut` is undefined');
+ if (!isValidAmount(amount, assetIn)) throw new Error('Invalid amount');
const addressIndex = getAddressIndex(assetIn.accountAddress);
diff --git a/apps/minifront/src/utils/empty-balance-response.ts b/apps/minifront/src/utils/empty-balance-response.ts
new file mode 100644
index 0000000000..9e7d892a61
--- /dev/null
+++ b/apps/minifront/src/utils/empty-balance-response.ts
@@ -0,0 +1,21 @@
+import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb';
+import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';
+import { AddressView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb';
+import { zeroValueView } from './zero-value-view';
+
+/**
+ * Transforms an asset metadata to a `BalanceResponse` with a zero balance on account 0.
+ */
+export const emptyBalanceResponse = (metadata: Metadata) => {
+ return new BalancesResponse({
+ balanceView: zeroValueView(metadata),
+ accountAddress: new AddressView({
+ addressView: {
+ case: 'decoded',
+ value: {
+ index: { account: 0 },
+ },
+ },
+ }),
+ });
+};
diff --git a/packages/ui/components/ui/balance-value-view.tsx b/packages/ui/components/ui/balance-value-view.tsx
index f8c8ea8c56..1f685fd186 100644
--- a/packages/ui/components/ui/balance-value-view.tsx
+++ b/packages/ui/components/ui/balance-value-view.tsx
@@ -11,9 +11,11 @@ import { cn } from '../../lib/utils';
*/
export const BalanceValueView = ({
valueView,
+ error,
onClick,
}: {
valueView: ValueView;
+ error?: boolean;
onClick?: (valueView: ValueView) => void;
}) => {
const exponent = getDisplayDenomExponentFromValueView.optional()(valueView);
@@ -24,6 +26,7 @@ export const BalanceValueView = ({
onClick(valueView) : undefined}
diff --git a/packages/ui/components/ui/icon-input.tsx b/packages/ui/components/ui/icon-input.tsx
index d70838a17a..ad20503922 100644
--- a/packages/ui/components/ui/icon-input.tsx
+++ b/packages/ui/components/ui/icon-input.tsx
@@ -1,21 +1,19 @@
import { ReactNode } from 'react';
import { Input } from './input';
-/**
- * Use this to render an input with an icon to its left, such as a search field
- * with a magnifying glass.
- */
-export const IconInput = ({
- value,
- onChange,
- icon,
- placeholder,
-}: {
+interface IconInputProps {
value: string;
onChange: (value: string) => void;
icon: ReactNode;
placeholder?: string;
-}) => {
+ autoFocus?: boolean;
+}
+
+/**
+ * Use this to render an input with an icon to its left, such as a search field
+ * with a magnifying glass.
+ */
+export const IconInput = ({ value, onChange, icon, placeholder, autoFocus }: IconInputProps) => {
return (
{icon}
@@ -24,6 +22,7 @@ export const IconInput = ({
onChange={e => onChange(e.target.value)}
variant='transparent'
placeholder={placeholder}
+ autoFocus={autoFocus}
/>
);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 23bdd02a40..7abe4bbcf6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -268,6 +268,9 @@ importers:
'@types/react-helmet':
specifier: ^6.1.11
version: 6.1.11
+ vite:
+ specifier: ^5.2.11
+ version: 5.3.1(@types/node@20.14.4)(terser@5.31.1)
apps/node-status:
dependencies: