From edf5263a125d8031b5c520440898aa0873c0ce2f Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Wed, 24 Sep 2025 09:18:52 +0400 Subject: [PATCH 1/2] fix(wallet/webui): honour resource divisibility in UI --- .../AssetVault/NFTs/components/NftParts.tsx | 50 ++++++----- .../src/routes/AssetVault/Tokens/Tokens.tsx | 83 +++++++++---------- .../Tokens/components/SendMoney.tsx | 41 +++++---- .../AssetVault/Tokens/steps/FormStep.tsx | 9 +- .../tari_walletd/web_ui/src/utils/helpers.tsx | 12 +-- 5 files changed, 107 insertions(+), 88 deletions(-) diff --git a/applications/tari_walletd/web_ui/src/routes/AssetVault/NFTs/components/NftParts.tsx b/applications/tari_walletd/web_ui/src/routes/AssetVault/NFTs/components/NftParts.tsx index d2d410fd3a..4b5167b5c8 100644 --- a/applications/tari_walletd/web_ui/src/routes/AssetVault/NFTs/components/NftParts.tsx +++ b/applications/tari_walletd/web_ui/src/routes/AssetVault/NFTs/components/NftParts.tsx @@ -29,13 +29,12 @@ import { NftCard as Card, DataTableCell } from "@components/StyledComponents"; import { convertCborValue } from "@utils/cbor"; import { shortenSubstateId, displayNftId } from "@utils/helpers"; import SendNft from "./SendNft"; - +import { Fragment } from "react/jsx-runtime"; function NftCard({ nft }: { nft: NonFungibleToken }) { const mutableData = convertCborValue(nft.mutable_data); - const data = convertCborValue(nft.data); + const data = convertCborValue(nft.data) as Record | undefined; const imageUrl = mutableData?.image_url; - const originalOwner = data?.original_owner; return ( @@ -59,19 +58,15 @@ function NftCard({ nft }: { nft: NonFungibleToken }) { {displayNftId(nft.nft_id)} - - ) : ( - - ) - } - label={nft.is_burnt ? "Burnt" : "Active"} - color={nft.is_burnt ? "error" : "success"} - size="small" - variant="outlined" - /> + {nft.is_burnt && ( + } + label={"Burnt"} + color={"error"} + size="small" + variant="outlined" + /> + )} @@ -81,10 +76,7 @@ function NftCard({ nft }: { nft: NonFungibleToken }) { - Original Owner: - - - + {data ? : null} @@ -93,6 +85,24 @@ function NftCard({ nft }: { nft: NonFungibleToken }) { ); } +function NftData({ data }: { data: Record }) { + return ( + <> + {Object.keys(data).map((key, i) => { + const value = data[key]; + return ( + + {key} + + + + + ); + })} + + ); +} + function NftRow({ nft }: { nft: NonFungibleToken }) { const mutableData = convertCborValue(nft.mutable_data); const data = convertCborValue(nft.data); diff --git a/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/Tokens.tsx b/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/Tokens.tsx index d0771b914b..c2e6455f53 100644 --- a/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/Tokens.tsx +++ b/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/Tokens.tsx @@ -150,17 +150,19 @@ function Tokens({ account }: { account: Account }) { return ( <> - setResourceToSend(null)} - onSendComplete={() => setResourceToSend(null)} - resource_address={resourceToSend?.address} - resource_type={resourceToSend?.resource_type!} - token_symbol={ - balancesData?.balances.find((b: BalanceEntry) => b.resource_address === resourceToSend?.address) - ?.token_symbol || "" - } - /> + {resourceToSend == null ? null : ( + setResourceToSend(null)} + onSendComplete={() => setResourceToSend(null)} + resource_address={resourceToSend?.address} + resource_type={resourceToSend?.resource_type!} + token_symbol={ + balancesData?.balances.find((b: BalanceEntry) => b.resource_address === resourceToSend?.address) + ?.token_symbol || "" + } + /> + )} - {balancesData?.balances.map( - ( - { - resource_address, - balance, - resource_type, - confidential_balance, - token_symbol, - vault_address, - divisibility, - }: BalanceEntry, - i: number, - ) => ( - void - } - /> - ), - )} + {balancesData?.balances + .filter((b) => BigInt(b.balance) > 0n || BigInt(b.confidential_balance) > 0n) + .map( + ( + { + resource_address, + balance, + resource_type, + confidential_balance, + token_symbol, + vault_address, + divisibility, + }: BalanceEntry, + i: number, + ) => ( + + ), + )} diff --git a/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx b/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx index 7bcb1dcd28..fe2edacc9c 100644 --- a/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx +++ b/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx @@ -80,9 +80,12 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) { .map((b: BalanceEntry) => b.resource_address) as string[]; // Find the available balance for the resource we're trying to send - const balanceEntry = data?.balances?.find( - (b: BalanceEntry) => b.resource_address === (props.resource_address || XTR), - ); + const balanceEntry = data?.balances?.find((b: BalanceEntry) => b.resource_address === props.resource_address); + + if (!balanceEntry) { + console.warn("No balance entry found for resource", props.resource_address); + return null; + } // Function to calculate available balance based on input selection const calculateAvailableBalance = () => { @@ -115,17 +118,6 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) { const availableBalance = calculateAvailableBalance(); - const transfer = { - account: substateIdToString(account.component_address), - amount: Math.floor((parseFloat(transferFormState.amount) || 0) * Math.pow(10, balanceEntry?.divisibility || 6)), - resource_address: props.resource_address!, - destination_address: transferFormState.address, - resourceType: props.resource_type, - output_to_revealed: !transferFormState.outputToConfidential, - input_selection: transferFormState.inputSelection as ConfidentialTransferInputSelection, - badge: transferFormState.badge, - }; - function setFormValue(e: React.ChangeEvent) { const { name, value } = e.target; @@ -179,14 +171,19 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) { if (!account || isEstimatingFee || !transferFormState.address.trim() || !transferFormState.amount) { return; } + if (!balanceEntry) { + console.warn("No balance entry found for resource", props.resource_address); + return; + } setIsEstimatingFee(true); try { + let amount = Math.floor((parseFloat(transferFormState.amount) || 0) * Math.pow(10, balanceEntry.divisibility)); // Create transfer object with current form state const currentTransfer = { account: substateIdToString(account.component_address), - amount: Math.floor((parseFloat(transferFormState.amount) || 0) * Math.pow(10, balanceEntry?.divisibility || 6)), + amount, resource_address: props.resource_address || XTR, destination_address: transferFormState.address, resourceType: props.resource_type, @@ -246,6 +243,18 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) { setActiveStep(2); try { + let amount = Math.floor((parseFloat(transferFormState.amount) || 0) * Math.pow(10, balanceEntry.divisibility)); + const transfer = { + account: substateIdToString(account.component_address), + amount, + resource_address: props.resource_address!, + destination_address: transferFormState.address, + resourceType: props.resource_type, + output_to_revealed: !transferFormState.outputToConfidential, + input_selection: transferFormState.inputSelection as ConfidentialTransferInputSelection, + badge: transferFormState.badge, + }; + await sendIt?.({ ...transfer, dry_run: false, @@ -299,7 +308,7 @@ export function SendMoneyDialog(props: SendMoneyDialogProps) { isEstimatingFee={isEstimatingFee} availableBalance={availableBalance} token_symbol={props.token_symbol} - divisibility={balanceEntry?.divisibility || 6} + divisibility={balanceEntry.divisibility} onSubmit={handleFormSubmit} onCancel={handleClose} onFormValueChange={setFormValue} diff --git a/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx b/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx index 846e6a171b..1a91894f95 100644 --- a/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx +++ b/applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx @@ -104,9 +104,8 @@ export default function FormStep({ } // Otherwise, show formatted value - const hasDecimals = amount.includes(".") && amount.split(".")[1].length > 0; return num.toLocaleString("en-US", { - minimumFractionDigits: hasDecimals ? 0 : 2, + minimumFractionDigits: 0, maximumFractionDigits: divisibility, }); }; @@ -205,13 +204,13 @@ export default function FormStep({ error={hasInsufficientFunds} helperText={ hasInsufficientFunds - ? `Insufficient funds. Available balance: ${formatDisplayCurrency(availableBalance || 0)}` + ? `Insufficient funds. Available balance: ${formatDisplayCurrency(availableBalance || 0, divisibility, token_symbol)}` : availableBalance !== undefined - ? `Available balance: ${formatDisplayCurrency(availableBalance)}` + ? `Available balance: ${formatDisplayCurrency(availableBalance, divisibility, token_symbol)}` : undefined } InputProps={{ - placeholder: "0.0", + placeholder: "0" + (divisibility > 0 ? "." + "0".repeat(divisibility) : ""), endAdornment: token_symbol ? {token_symbol} : undefined, }} /> diff --git a/applications/tari_walletd/web_ui/src/utils/helpers.tsx b/applications/tari_walletd/web_ui/src/utils/helpers.tsx index 209e274897..1dd04ffaf7 100644 --- a/applications/tari_walletd/web_ui/src/utils/helpers.tsx +++ b/applications/tari_walletd/web_ui/src/utils/helpers.tsx @@ -277,16 +277,18 @@ export const formatCurrency = (amount: number | bigint): string => { } }; -// Helper function for formatting amounts that are already in display units (XTR) -export const formatDisplayCurrency = (amount: number): string => { - const currencySymbol = useCurrencyStore.getState().currencySymbol; - +// Helper function for formatting currency amounts +export const formatDisplayCurrency = ( + amount: number, + divisibility: number, + currencySymbol: string | undefined, +): string => { if (isNaN(amount)) { return `0 ${currencySymbol}`; } return `${amount.toLocaleString("en-US", { minimumFractionDigits: 0, - maximumFractionDigits: CURRENCY.DECIMALS, + maximumFractionDigits: divisibility, })} ${currencySymbol}`; }; From 72db080c990d5093cb51b32d5509ed4004a37c65 Mon Sep 17 00:00:00 2001 From: Stan Bondi Date: Wed, 24 Sep 2025 09:32:26 +0400 Subject: [PATCH 2/2] fix: order utxos by id desc --- .../web_ui/src/routes/StealthUtxoList/StealthUtxoList.tsx | 4 +++- crates/wallet/storage_sqlite/src/reader.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/applications/tari_walletd/web_ui/src/routes/StealthUtxoList/StealthUtxoList.tsx b/applications/tari_walletd/web_ui/src/routes/StealthUtxoList/StealthUtxoList.tsx index 00d972bdbb..40b11464ea 100644 --- a/applications/tari_walletd/web_ui/src/routes/StealthUtxoList/StealthUtxoList.tsx +++ b/applications/tari_walletd/web_ui/src/routes/StealthUtxoList/StealthUtxoList.tsx @@ -109,7 +109,9 @@ function StealthUtxoList({ account }: { account: Account }) { {shortenString(utxo.address.id)} - {bigintToDecimalString(utxo.value, 6)} {currencySymbol} + + {bigintToDecimalString(utxo.value, 6)} {currencySymbol} + diff --git a/crates/wallet/storage_sqlite/src/reader.rs b/crates/wallet/storage_sqlite/src/reader.rs index 22c12cef33..8ea57e6b7c 100644 --- a/crates/wallet/storage_sqlite/src/reader.rs +++ b/crates/wallet/storage_sqlite/src/reader.rs @@ -952,6 +952,7 @@ impl WalletStoreReader for ReadTransaction<'_> { } let rows = query + .order_by(stealth_outputs::id.desc()) .get_results::<(models::StealthOutput, String)>(self.connection()) .map_err(|e| WalletStorageError::general(OPERATION, e))?;