diff --git a/apps/core/src/components/coin/AmountWithFiat.tsx b/apps/core/src/components/coin/AmountWithFiat.tsx
new file mode 100644
index 0000000000..6982a7cf15
--- /dev/null
+++ b/apps/core/src/components/coin/AmountWithFiat.tsx
@@ -0,0 +1,41 @@
+// Copyright (c) 2026 IOTA Stiftung
+// SPDX-License-Identifier: Apache-2.0
+
+import { useIotaClientContext } from '@iota/dapp-kit';
+import { type Network } from '@iota/iota-sdk/client';
+import { IOTA_TYPE_ARG } from '@iota/iota-sdk/utils';
+import { useBalanceInUSD } from '../../hooks';
+import { CoinFiatValue } from './CoinFiatValue';
+
+export interface AmountWithFiatProps {
+ amount: bigint | string | number;
+ formatted: string;
+ symbol?: string;
+ coinType?: string;
+}
+
+export function AmountWithFiat({
+ amount,
+ formatted,
+ symbol,
+ coinType = IOTA_TYPE_ARG,
+}: AmountWithFiatProps) {
+ const { network } = useIotaClientContext();
+ const value = useBalanceInUSD(coinType, amount, network as Network);
+ const hasFiatValue = value !== null && value !== undefined && Math.abs(value) >= 0.005;
+
+ return (
+
+
+ {formatted}
+ {symbol ? ` ${symbol}` : ''}
+
+ {hasFiatValue && (
+
+ ~
+
+
+ )}
+
+ );
+}
diff --git a/apps/core/src/components/coin/index.ts b/apps/core/src/components/coin/index.ts
index f994c08ab9..48fd4bc17f 100644
--- a/apps/core/src/components/coin/index.ts
+++ b/apps/core/src/components/coin/index.ts
@@ -5,3 +5,4 @@ export * from './CoinIcon';
export * from './CoinSelector';
export * from './CoinItem';
export * from './CoinFiatValue';
+export * from './AmountWithFiat';
diff --git a/apps/core/src/components/stake/StakedCard.tsx b/apps/core/src/components/stake/StakedCard.tsx
index 743c5a8693..6a166ba8f2 100644
--- a/apps/core/src/components/stake/StakedCard.tsx
+++ b/apps/core/src/components/stake/StakedCard.tsx
@@ -17,6 +17,7 @@ import { ExtendedDelegatedStake } from '../../utils';
import { useFormatCoin, useStakeRewardStatus, useGetInactiveValidator } from '../../hooks';
import { RewardsOff, Warning } from '@iota/apps-ui-icons';
import { useIotaClientQuery } from '@iota/dapp-kit';
+import { AmountWithFiat } from '../coin';
interface StakedCardProps {
extendedStake: ExtendedDelegatedStake;
@@ -68,7 +69,13 @@ export function StakedCard({
+ }
icon={
activeButNotInTheCommittee ? (
diff --git a/apps/core/src/components/stake/UnstakeBreakdown.tsx b/apps/core/src/components/stake/UnstakeBreakdown.tsx
index 0f6329c9c0..9beccba404 100644
--- a/apps/core/src/components/stake/UnstakeBreakdown.tsx
+++ b/apps/core/src/components/stake/UnstakeBreakdown.tsx
@@ -5,6 +5,7 @@ import { Divider, KeyValueInfo } from '@iota/apps-ui-kit';
import { GAS_SYMBOL } from '../../constants';
import { useFormatCoin } from '../../hooks/useFormatCoin';
import type { UnstakeAmounts } from '../../utils/stake/calculateUnstakeAmounts';
+import { AmountWithFiat } from '../coin';
interface UnstakeBreakdownProps {
isPartialUnstake: boolean;
@@ -32,40 +33,70 @@ export function UnstakeBreakdown({ isPartialUnstake, unstakeAmounts }: UnstakeBr
<>
+ }
fullwidth
/>
+ }
fullwidth
/>
+ }
fullwidth
/>
+ }
fullwidth
/>
+ }
fullwidth
/>
+ }
fullwidth
/>
>
@@ -76,21 +107,36 @@ export function UnstakeBreakdown({ isPartialUnstake, unstakeAmounts }: UnstakeBr
<>
+ }
fullwidth
/>
+ }
fullwidth
/>
+ }
fullwidth
/>
>
diff --git a/apps/core/src/hooks/useGetStakingValidatorDetails.ts b/apps/core/src/hooks/useGetStakingValidatorDetails.ts
index dce7ec33e3..001dcc5d48 100644
--- a/apps/core/src/hooks/useGetStakingValidatorDetails.ts
+++ b/apps/core/src/hooks/useGetStakingValidatorDetails.ts
@@ -81,6 +81,7 @@ export function useGetStakingValidatorDetails({
totalStake: totalStakeFormatted,
totalStakeOriginal: totalStake,
totalValidatorsStake: totalValidatorsStakeFormatted,
+ totalValidatorsStakeOriginal: totalValidatorStake,
totalStakePercentage,
validatorApy,
systemDataResult,
diff --git a/apps/ui-kit/src/lib/components/molecules/input/InputWrapper.tsx b/apps/ui-kit/src/lib/components/molecules/input/InputWrapper.tsx
index 6c06934ff9..fb3a20a934 100644
--- a/apps/ui-kit/src/lib/components/molecules/input/InputWrapper.tsx
+++ b/apps/ui-kit/src/lib/components/molecules/input/InputWrapper.tsx
@@ -5,6 +5,7 @@ import cx from 'classnames';
import { SecondaryText } from '@/components/atoms/secondary-text';
import { LABEL_CLASSES } from './input.classes';
import { createElement } from 'react';
+import type { ReactNode } from 'react';
export enum LabelHtmlTag {
Label = 'label',
@@ -19,7 +20,7 @@ export interface InputWrapperProps {
/**
* Shows a caption with the text below the input.
*/
- caption?: string;
+ caption?: ReactNode;
/**
* Error Message. Overrides the caption.
*/
diff --git a/apps/wallet-dashboard/app/(protected)/staking/page.tsx b/apps/wallet-dashboard/app/(protected)/staking/page.tsx
index f5b53b2109..50c7f94e69 100644
--- a/apps/wallet-dashboard/app/(protected)/staking/page.tsx
+++ b/apps/wallet-dashboard/app/(protected)/staking/page.tsx
@@ -34,6 +34,7 @@ import {
useGetAllOwnedObjects,
TIMELOCK_IOTA_TYPE,
mapTimelockObjects,
+ AmountWithFiat,
} from '@iota/core';
import { useCurrentAccount, useIotaClient, useIotaClientQuery } from '@iota/dapp-kit';
import { useMemo } from 'react';
@@ -167,13 +168,23 @@ function StakingDashboardPage(): React.JSX.Element {
+ }
/>
+ }
/>
diff --git a/apps/wallet-dashboard/components/dialogs/staking/views/DetailsView.tsx b/apps/wallet-dashboard/components/dialogs/staking/views/DetailsView.tsx
index a4880b3cee..8177ab4ab6 100644
--- a/apps/wallet-dashboard/components/dialogs/staking/views/DetailsView.tsx
+++ b/apps/wallet-dashboard/components/dialogs/staking/views/DetailsView.tsx
@@ -14,6 +14,7 @@ import {
useIsActiveValidator,
useGetNextEpochCommitteeMember,
useGetInactiveValidator,
+ AmountWithFiat,
} from '@iota/core';
import {
Header,
@@ -164,14 +165,24 @@ export function DetailsView({
+ }
fullwidth
/>
+ }
fullwidth
/>
diff --git a/apps/wallet-dashboard/components/dialogs/staking/views/EnterAmountDialogLayout.tsx b/apps/wallet-dashboard/components/dialogs/staking/views/EnterAmountDialogLayout.tsx
index f3abec741d..99ece258c7 100644
--- a/apps/wallet-dashboard/components/dialogs/staking/views/EnterAmountDialogLayout.tsx
+++ b/apps/wallet-dashboard/components/dialogs/staking/views/EnterAmountDialogLayout.tsx
@@ -35,7 +35,7 @@ interface FormValues {
interface EnterAmountDialogLayoutProps {
selectedValidator: string;
senderAddress: string;
- caption: string;
+ caption: React.ReactNode;
renderInfo?: React.JSX.Element;
isLoading: boolean;
onBack: () => void;
diff --git a/apps/wallet-dashboard/components/dialogs/staking/views/EnterAmountView.tsx b/apps/wallet-dashboard/components/dialogs/staking/views/EnterAmountView.tsx
index 71dfc5ff76..278c3dbbe2 100644
--- a/apps/wallet-dashboard/components/dialogs/staking/views/EnterAmountView.tsx
+++ b/apps/wallet-dashboard/components/dialogs/staking/views/EnterAmountView.tsx
@@ -9,6 +9,7 @@ import {
getGasBudgetErrorMessage,
NO_BALANCE_GENERIC_MESSAGE,
useValidatorInfo,
+ AmountWithFiat,
} from '@iota/core';
import { CoinFormat, IOTA_TYPE_ARG, parseAmount } from '@iota/iota-sdk/utils';
import { useFormikContext } from 'formik';
@@ -73,9 +74,18 @@ export function EnterAmountView({
balance: availableBalance,
format: CoinFormat.Full,
});
- const caption = availableBalance
- ? `${availableBalanceFormatted} ${availableBalanceFormattedSymbol} Available`
- : '--';
+ const caption = availableBalance ? (
+
+
+ Available
+
+ ) : (
+ '--'
+ );
const gasUnstakeBuffer = gasSummary?.budget ? BigInt(gasSummary.budget) * BigInt(2) : BigInt(0);
const maxSafeAmount = availableBalance - gasUnstakeBuffer;
diff --git a/apps/wallet-dashboard/components/dialogs/staking/views/EnterTimelockedAmountView.tsx b/apps/wallet-dashboard/components/dialogs/staking/views/EnterTimelockedAmountView.tsx
index 4d2df3cf3c..ec77d874eb 100644
--- a/apps/wallet-dashboard/components/dialogs/staking/views/EnterTimelockedAmountView.tsx
+++ b/apps/wallet-dashboard/components/dialogs/staking/views/EnterTimelockedAmountView.tsx
@@ -11,6 +11,7 @@ import {
toast,
getGasBudgetErrorMessage,
useCoinMetadata,
+ AmountWithFiat,
} from '@iota/core';
import { CoinFormat, IOTA_TYPE_ARG, parseAmount } from '@iota/iota-sdk/utils';
import { useFormikContext } from 'formik';
@@ -96,7 +97,16 @@ export function EnterTimelockedAmountView({
format: CoinFormat.Full,
});
- const caption = `${maxTokenFormatted} ${maxTokenFormattedSymbol} Available`;
+ const caption = (
+
+
+ Available
+
+ );
const info = useMemo(() => {
if (isSearchingProtocolMaxAmount) {
let message = 'The current amount is not valid due to the large number of objects. ';
diff --git a/apps/wallet-dashboard/components/dialogs/staking/views/StakedInfo.tsx b/apps/wallet-dashboard/components/dialogs/staking/views/StakedInfo.tsx
index 66931587cc..8bb8849f97 100644
--- a/apps/wallet-dashboard/components/dialogs/staking/views/StakedInfo.tsx
+++ b/apps/wallet-dashboard/components/dialogs/staking/views/StakedInfo.tsx
@@ -5,6 +5,7 @@ import {
formatPercentageDisplay,
useValidatorInfo,
useGetStakingValidatorDetails,
+ AmountWithFiat,
} from '@iota/core';
import { KeyValueInfo, Panel, TooltipPosition } from '@iota/apps-ui-kit';
@@ -18,13 +19,19 @@ export function StakedInfo({ validatorAddress, accountAddress }: StakedInfoProps
validatorAddress: validatorAddress,
});
- const { totalValidatorsStake, totalStakePercentage, totalStake, effectiveCommission } =
- useGetStakingValidatorDetails({
- accountAddress: accountAddress,
- stakeId: null,
- validatorAddress: validatorAddress,
- unstake: false,
- });
+ const {
+ totalValidatorsStake,
+ totalValidatorsStakeOriginal,
+ totalStakePercentage,
+ totalStake,
+ totalStakeOriginal,
+ effectiveCommission,
+ } = useGetStakingValidatorDetails({
+ accountAddress: accountAddress,
+ stakeId: null,
+ validatorAddress: validatorAddress,
+ unstake: false,
+ });
const [totalValidatorStakeFormatted, totalValidatorStakeSymbol] = totalValidatorsStake;
const [totalStakeFormatted, totalStakeSymbol] = totalStake;
@@ -57,16 +64,26 @@ export function StakedInfo({ validatorAddress, accountAddress }: StakedInfoProps
keyText="Total Staked"
tooltipPosition={TooltipPosition.Right}
tooltipText="The full amount of IOTA staked by this validator and delegators for network validation and rewards."
- value={totalValidatorStakeFormatted}
- supportingLabel={totalValidatorStakeSymbol}
+ value={
+
+ }
fullwidth
/>
+ }
fullwidth
/>
diff --git a/apps/wallet-dashboard/components/staking-overview/StakingData.tsx b/apps/wallet-dashboard/components/staking-overview/StakingData.tsx
index 0b4893c1de..a6a9194a6a 100644
--- a/apps/wallet-dashboard/components/staking-overview/StakingData.tsx
+++ b/apps/wallet-dashboard/components/staking-overview/StakingData.tsx
@@ -9,6 +9,7 @@ import {
useTotalDelegatedStake,
useBalanceVisible,
BALANCE_MASK,
+ AmountWithFiat,
} from '@iota/core';
import { DelegatedStake } from '@iota/iota-sdk/client';
interface StakingDataProps {
@@ -36,13 +37,18 @@ export function StakingData({ stakingData }: StakingDataProps) {
size={LabelTextSize.Large}
label="Staked"
text={
- stakeResult.isPending
- ? '-'
- : isBalanceVisible
- ? `${formattedDelegatedStake}`
- : BALANCE_MASK
+ stakeResult.isPending ? (
+ '-'
+ ) : isBalanceVisible ? (
+
+ ) : (
+ `${BALANCE_MASK} ${stakeSymbol}`
+ )
}
- supportingLabel={stakeSymbol}
/>
@@ -50,13 +56,18 @@ export function StakingData({ stakingData }: StakingDataProps) {
size={LabelTextSize.Large}
label="Earned"
text={
- rewardsResult.isPending
- ? '-'
- : isBalanceVisible
- ? `${formattedDelegatedRewards}`
- : BALANCE_MASK
+ rewardsResult.isPending ? (
+ '-'
+ ) : isBalanceVisible ? (
+
+ ) : (
+ `${BALANCE_MASK} ${rewardsSymbol}`
+ )
}
- supportingLabel={rewardsSymbol}
/>
diff --git a/apps/wallet/src/ui/app/pages/home/tokens/coin-balance/index.tsx b/apps/wallet/src/ui/app/pages/home/tokens/coin-balance/index.tsx
index 7e67d8c135..ca5c5fa759 100644
--- a/apps/wallet/src/ui/app/pages/home/tokens/coin-balance/index.tsx
+++ b/apps/wallet/src/ui/app/pages/home/tokens/coin-balance/index.tsx
@@ -1,9 +1,8 @@
// Copyright (c) Mysten Labs, Inc.
// Modifications Copyright (c) 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
-import { formatBalanceToUSD, useBalanceInUSD, useFormatCoin, BALANCE_MASK } from '@iota/core';
-import { IOTA_TYPE_ARG, CoinFormat, formatBalance } from '@iota/iota-sdk/utils';
-import { useMemo } from 'react';
+import { CoinFiatValue, useBalanceInUSD, useFormatCoin, BALANCE_MASK } from '@iota/core';
+import { CoinFormat, formatBalance } from '@iota/iota-sdk/utils';
import { Button, ButtonSize, ButtonType, Tooltip, TooltipPosition } from '@iota/apps-ui-kit';
import BigNumber from 'bignumber.js';
import { useAppSelector, useBalanceVisibility } from '_src/ui/app/hooks';
@@ -16,27 +15,33 @@ export interface CoinProps {
interface WalletBalanceUsdProps {
amount: bigint;
+ coinType: string;
isVisible: boolean;
}
-function WalletBalanceUsd({ amount: walletBalance, isVisible }: WalletBalanceUsdProps) {
+function WalletBalanceUsd({ amount: walletBalance, coinType, isVisible }: WalletBalanceUsdProps) {
const network = useAppSelector((state) => state.app.network);
- const formattedWalletBalance = useBalanceInUSD(IOTA_TYPE_ARG, walletBalance, network);
+ const usdValue = useBalanceInUSD(coinType, walletBalance, network);
- const walletBalanceInUsd = useMemo(() => {
- if (!formattedWalletBalance) return null;
-
- return `~${formatBalanceToUSD(formattedWalletBalance)}`;
- }, [formattedWalletBalance]);
-
- if (!walletBalanceInUsd) {
+ if (usdValue === null || usdValue === undefined || Math.abs(usdValue) < 0.005) {
return null;
}
return (
-
-
{isVisible ? walletBalanceInUsd : BALANCE_MASK}
-
USD
+
+ {isVisible ? (
+ <>
+ ~
+
+ USD
+ >
+ ) : (
+ {BALANCE_MASK}
+ )}
);
}
@@ -96,7 +101,7 @@ export function CoinBalance({ amount: walletBalance, type }: CoinProps) {
/>
-
+
>
);
}
diff --git a/apps/wallet/src/ui/app/staking/delegation-detail/DelegationDetailCard.tsx b/apps/wallet/src/ui/app/staking/delegation-detail/DelegationDetailCard.tsx
index c0be7b8cd5..99fc17ecaa 100644
--- a/apps/wallet/src/ui/app/staking/delegation-detail/DelegationDetailCard.tsx
+++ b/apps/wallet/src/ui/app/staking/delegation-detail/DelegationDetailCard.tsx
@@ -21,6 +21,7 @@ import {
useIsValidatorCommitteeMember,
useIsActiveValidator,
useGetNextEpochCommitteeMember,
+ AmountWithFiat,
} from '@iota/core';
import { Network, type StakeObject } from '@iota/iota-sdk/client';
import { IOTA_TYPE_ARG, CoinFormat } from '@iota/iota-sdk/utils';
@@ -183,14 +184,24 @@ export function DelegationDetailCard({ validatorAddress, stakedId }: DelegationD
+ }
fullwidth
/>
+ }
fullwidth
/>
diff --git a/apps/wallet/src/ui/app/staking/stake/StakeForm.tsx b/apps/wallet/src/ui/app/staking/stake/StakeForm.tsx
index 1407bdb1a0..fe25a6f6b9 100644
--- a/apps/wallet/src/ui/app/staking/stake/StakeForm.tsx
+++ b/apps/wallet/src/ui/app/staking/stake/StakeForm.tsx
@@ -16,6 +16,7 @@ import {
NO_BALANCE_GENERIC_MESSAGE,
getGasBudgetErrorMessage,
useGetValidatorsApy,
+ AmountWithFiat,
} from '@iota/core';
import * as Sentry from '@sentry/react';
import { ampli } from '_src/shared/analytics/ampli';
@@ -242,9 +243,18 @@ export function StakeFormComponent({ validatorAddress, epoch, onSuccess }: Stake
placeholder={`0 ${symbol}`}
value={amount}
caption={
- minAmountTxGasBudget
- ? `${availableBalanceFormatted} ${symbol} Available`
- : '--'
+ minAmountTxGasBudget ? (
+
+
+ Available
+
+ ) : (
+ '--'
+ )
}
suffix={' ' + symbol}
errorMessage={amount && meta.error ? meta.error : undefined}
diff --git a/apps/wallet/src/ui/app/staking/stake/ValidatorFormDetail.tsx b/apps/wallet/src/ui/app/staking/stake/ValidatorFormDetail.tsx
index 77082cb3fc..3dabb3904e 100644
--- a/apps/wallet/src/ui/app/staking/stake/ValidatorFormDetail.tsx
+++ b/apps/wallet/src/ui/app/staking/stake/ValidatorFormDetail.tsx
@@ -6,6 +6,7 @@ import {
EFFECTIVE_COMMISSION_TOOLTIP,
formatPercentageDisplay,
useGetStakingValidatorDetails,
+ AmountWithFiat,
} from '@iota/core';
import { useSearchParams } from 'react-router-dom';
import { useActiveAddress } from '_hooks';
@@ -34,7 +35,9 @@ export function ValidatorFormDetail({ validatorAddress, unstake }: ValidatorForm
totalStakePercentage,
validatorApy: { apy, isApyApproxZero },
totalValidatorsStake: [totalValidatorStakeFormatted, totalValidatorStakeSymbol],
+ totalValidatorsStakeOriginal,
totalStake: [totalStakeFormatted, totalStakeSymbol],
+ totalStakeOriginal,
delegatedStakeDataResult,
systemDataResult,
effectiveCommission,
@@ -102,16 +105,26 @@ export function ValidatorFormDetail({ validatorAddress, unstake }: ValidatorForm
keyText="Total Staked"
tooltipPosition={TooltipPosition.Bottom}
tooltipText="The full amount of IOTA staked by this validator and delegators for network validation and rewards."
- value={totalValidatorStakeFormatted}
- supportingLabel={totalValidatorStakeSymbol}
+ value={
+
+ }
fullwidth
/>
+ }
fullwidth
/>
>
diff --git a/apps/wallet/src/ui/app/staking/validators/ValidatorsCard.tsx b/apps/wallet/src/ui/app/staking/validators/ValidatorsCard.tsx
index 18ac89f894..c20337500f 100644
--- a/apps/wallet/src/ui/app/staking/validators/ValidatorsCard.tsx
+++ b/apps/wallet/src/ui/app/staking/validators/ValidatorsCard.tsx
@@ -12,6 +12,7 @@ import {
DELEGATED_STAKES_QUERY_STALE_TIME,
useFormatCoin,
StakedCard,
+ AmountWithFiat,
} from '@iota/core';
import { useMemo } from 'react';
import { useActiveAddress } from '_hooks';
@@ -115,13 +116,23 @@ export function ValidatorsCard() {
+ }
/>
+ }
/>