fix(wallet/webui): honour resource divisibility in UI - #1582
Conversation
WalkthroughRender NFT metadata as generic key/value pairs and simplify burn status rendering; tighten token send flow with conditional dialog mounting, exact balance lookups, unified divisibility-based amount handling and formatting; change currency formatter signature; add ordering to stealth outputs query and a minor formatting edit in the Stealth UTXO list. Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant N as NFT UI
participant D as NftData component
U->>N: View NFT card/row
alt nft.data exists
N->>D: Render key/value pairs from nft.data
D-->>N: Display keys with CopyAddress formatting
else no data
N-->>N: Skip NftData block
end
N-->>U: Show chip only if nft.is_burnt == true
note right of N: Original owner inline display removed
sequenceDiagram
participant U as User
participant T as Tokens Page
participant D as SendMoneyDialog
participant H as Helpers (formatDisplayCurrency)
participant S as Submit Transfer
U->>T: Click "Send" on token
alt resourceToSend exists
T->>D: Mount & open dialog
D->>D: Lookup exact balanceEntry by resource_address
alt balanceEntry missing
D-->>T: Log warning, return/null (no dialog flow)
else balanceEntry present
U->>D: Enter amount + recipient
D->>H: Scale/format amount using divisibility
D->>D: estimateFee using scaled amount
U->>D: Confirm
D->>S: Build transfer payload using validated balanceEntry & scaled amount
S-->>D: Success / Error
D-->>T: Close dialog on completion
end
else
T-->>U: No dialog mounted
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labelsP-acks_required, P-reviews_required Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
83e314c to
72db080
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (12)
applications/tari_walletd/web_ui/src/routes/AssetVault/NFTs/components/NftParts.tsx (5)
88-105: Make NftData robust: stable keys and type-aware value rendering.
- Avoid index keys; use the actual key.
- Handle non-string values (numbers, booleans, objects) without forcing
[object Object].- Align prop type with the guard above.
Apply this diff:
-function NftData({ data }: { data: Record<string, any> }) { - return ( - <> - {Object.keys(data).map((key, i) => { - const value = data[key]; - return ( - <Fragment key={i}> - <Typography variant="subtitle2">{key}</Typography> - <Typography variant="body2" color="text.secondary" gutterBottom> - <CopyAddress address={String(value)} /> - </Typography> - </Fragment> - ); - })} - </> - ); -} +function NftData({ data }: { data: Record<string, unknown> }) { + return ( + <> + {Object.entries(data).map(([key, value]) => ( + <Fragment key={key}> + <Typography variant="subtitle2">{key}</Typography> + <Typography variant="body2" color="text.secondary" gutterBottom> + {typeof value === "string" ? ( + <CopyAddress address={value} /> + ) : ( + String( + typeof value === "object" && value !== null + ? JSON.stringify(value) + : value + ) + )} + </Typography> + </Fragment> + ))} + </> + ); +}
32-32: Import Fragment from 'react' (or keep <> if you don't need keyed fragments).Importing from "react/jsx-runtime" is atypical; prefer from "react" for keyed fragments.
-import { Fragment } from "react/jsx-runtime"; +import { Fragment } from "react";
51-55: Type the onError handler to avoidanyand usecurrentTarget.Small TS hygiene and correctness improvement.
- onError={(e: any) => { - e.target.src = + onError={(e: React.SyntheticEvent<HTMLImageElement>) => { + e.currentTarget.src = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjIwMCIgdmlld0JveD0iMCAwIDMwMCAyMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIzMDAiIGhlaWdodD0iMjAwIiBmaWxsPSIjRjVGNUY1Ii8+CjxwYXRoIGQ9Ik0xMjUgNzVIMTc1VjEyNUgxMjVWNzVaIiBmaWxsPSIjRERERUREIi8+CjxwYXRoIGQ9Ik0xNDAgOTBIMTYwVjExMEgxNDBWOTBaIiBmaWxsPSIjQkJCQkJCIi8+Cjx0ZXh0IHg9IjE1MCIgeT0iMTQwIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiM5OTk5OTkiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk5GVDwvdGV4dD4KPC9zdmc+"; }}
139-142: Avoid rendering a “blank address” when original owner is missing.Rendering
CopyAddresswith""produces an empty/odd UI. Guard this cell.- <Typography variant="body2"> - <CopyAddress address={originalOwner || ""} /> - </Typography> + {typeof originalOwner === "string" && originalOwner.length > 0 ? ( + <Typography variant="body2"> + <CopyAddress address={originalOwner} /> + </Typography> + ) : null}
144-156: Align status chip behavior with card (show only when burnt).Unifying reduces visual noise and keeps status semantics consistent.
- <Chip - icon={ - nft.is_burnt ? ( - <CancelRoundedIcon style={{ height: 16, width: 16 }} /> - ) : ( - <CheckCircleRoundedIcon style={{ height: 16, width: 16 }} /> - ) - } - label={nft.is_burnt ? "Burnt" : "Active"} - color={nft.is_burnt ? "error" : "success"} - size="small" - variant="outlined" - /> + {nft.is_burnt && ( + <Chip + icon={<CancelRoundedIcon style={{ height: 16, width: 16 }} />} + label="Burnt" + color="error" + size="small" + variant="outlined" + /> + )}crates/wallet/storage_sqlite/src/reader.rs (2)
837-848: Apply the same explicit ordering to keep list views consistent.This path returns unspent stealth outputs without an explicit order. Align it with
stealth_outputs_get_manyto avoid UI jitter and ensure deterministic results.let rows = stealth_outputs::table .filter( stealth_outputs::owner_account_id.eq(accounts::table .select(accounts::id) .filter(accounts::address.eq(account_addr.to_string())) .limit(1) .single_value() .assume_not_null()), ) .filter(stealth_outputs::status.eq(OutputStatus::Unspent.as_key_str())) + .order_by(stealth_outputs::id.desc()) .get_results::<models::StealthOutput>(self.connection()) .map_err(|e| WalletStorageError::general(OPERATION, e))?;
860-864: Order locked stealth outputs for deterministic display.Mirror the “newest first” behavior here as well.
let rows = stealth_outputs::table .filter(stealth_outputs::lock_id.eq(lock_id)) + .order_by(stealth_outputs::id.desc()) .get_results::<models::StealthOutput>(self.connection()) .map_err(|e| WalletStorageError::general(OPERATION, e))?;applications/tari_walletd/web_ui/src/utils/helpers.tsx (1)
280-293: Avoid "undefined" symbol and clamp fraction digitsGuard divisibility, and don’t append a trailing space or "undefined" when no symbol is provided.
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: divisibility, - })} ${currencySymbol}`; + const maxFrac = Math.max(0, Math.min(20, Number.isFinite(divisibility) ? divisibility : 0)); + const suffix = currencySymbol ? ` ${currencySymbol}` : ""; + if (isNaN(amount)) { + return `0${suffix}`; + } + return `${amount.toLocaleString("en-US", { + minimumFractionDigits: 0, + maximumFractionDigits: maxFrac, + })}${suffix}`; };applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/Tokens.tsx (1)
207-234: Use a stable key for rowsAvoid index-based keys to prevent row mis-association on list changes.
- <BalanceRow - key={i} + <BalanceRow + key={resource_address} token_symbol={token_symbol || ""} resource_address={resource_address}applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx (1)
213-215: Placeholder length nitFor very large divisibility, the placeholder can become long. Consider capping the visible zeros (e.g., show up to 8) while still allowing full precision input.
Example:
- placeholder: "0" + (divisibility > 0 ? "." + "0".repeat(divisibility) : ""), + placeholder: "0" + (divisibility > 0 ? "." + "0".repeat(Math.min(8, divisibility)) : ""),applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx (2)
83-88: Good guard; consider graceful UX instead of nullReturning null avoids crashes, but consider auto-closing with a user-visible error to prevent a silent no-op.
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; + try { props.handleClose?.(); } catch {} + return null; }
330-331: Unify divisibility source; remove fallbackGiven the non-null balanceEntry guard, pass the exact divisibility here too.
- divisibility={balanceEntry?.divisibility || 6} + divisibility={balanceEntry.divisibility}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
applications/tari_walletd/web_ui/src/routes/AssetVault/NFTs/components/NftParts.tsx(4 hunks)applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/Tokens.tsx(2 hunks)applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx(4 hunks)applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx(2 hunks)applications/tari_walletd/web_ui/src/routes/StealthUtxoList/StealthUtxoList.tsx(1 hunks)applications/tari_walletd/web_ui/src/utils/helpers.tsx(1 hunks)crates/wallet/storage_sqlite/src/reader.rs(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
applications/tari_walletd/web_ui/src/routes/StealthUtxoList/StealthUtxoList.tsx (1)
applications/tari_walletd/web_ui/src/utils/helpers.tsx (1)
bigintToDecimalString(233-253)
crates/wallet/storage_sqlite/src/reader.rs (1)
crates/wallet/storage_sqlite/src/writer.rs (9)
stealth_outputs(1010-1024)stealth_outputs(1092-1092)stealth_outputs(1093-1093)stealth_outputs(1122-1122)stealth_outputs(1123-1123)stealth_outputs(1135-1135)stealth_outputs(1136-1136)stealth_outputs(1155-1155)stealth_outputs(1156-1156)
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx (3)
bindings/src/types/wallet-daemon-client/BalanceEntry.ts (1)
BalanceEntry(7-15)applications/tari_walletd/web_ui/src/utils/helpers.tsx (1)
substateIdToString(100-109)bindings/src/types/ConfidentialTransferInputSelection.ts (1)
ConfidentialTransferInputSelection(3-7)
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/Tokens.tsx (2)
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx (1)
SendMoneyDialog(52-346)bindings/src/types/wallet-daemon-client/BalanceEntry.ts (1)
BalanceEntry(7-15)
applications/tari_walletd/web_ui/src/routes/AssetVault/NFTs/components/NftParts.tsx (2)
bindings/src/types/NonFungibleToken.ts (1)
NonFungibleToken(6-13)applications/tari_walletd/web_ui/src/components/CopyAddress.tsx (1)
CopyAddress(32-43)
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx (1)
applications/tari_walletd/web_ui/src/utils/helpers.tsx (1)
formatDisplayCurrency(281-293)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: test
- GitHub Check: check stable
- GitHub Check: check nightly
- GitHub Check: clippy
🔇 Additional comments (9)
applications/tari_walletd/web_ui/src/routes/StealthUtxoList/StealthUtxoList.tsx (1)
112-114: Use resource divisibility and symbol; avoid hard-coded 6 and global currency symbolThis still formats with 6 decimals and a global symbol, which contradicts the PR objective to honor per‑resource divisibility/symbol. Derive both from the selected stealth resource metadata.
Apply this diff to the value cell:
- <DataTableCell> - {bigintToDecimalString(utxo.value, 6)} {currencySymbol} - </DataTableCell> + <DataTableCell> + {bigintToDecimalString(utxo.value, divisibility)} {symbol} + </DataTableCell>Add these definitions near where
resourceToUseis computed (e.g., after Line 43):// Derive divisibility/symbol from the selected stealth resource; fall back sensibly const stealthMeta = stealthResources.find((b) => b.resource_address === resourceToUse); const divisibility = (stealthMeta as any)?.divisibility ?? (stealthMeta as any)?.decimals ?? 6; // fallback if metadata missing const symbol = (stealthMeta as any)?.symbol ?? currencySymbol;Additionally, to safely support higher divisibility (e.g., 18), fix bigint exponentiation to avoid Number precision in bigintToDecimalString:
applications/tari_walletd/web_ui/src/utils/helpers.tsx:
- const wholeValues = (number / BigInt(10 ** decimalPlaces)).toLocaleString(locale, { + const wholeValues = (number / (BigInt(10) ** BigInt(decimalPlaces))).toLocaleString(locale, {Please verify the actual metadata field names (divisibility/decimals/symbol) on balances and adjust the casts accordingly.
applications/tari_walletd/web_ui/src/routes/AssetVault/NFTs/components/NftParts.tsx (2)
61-69: LGTM: Burn status chip only when burnt.This avoids showing an “Active” chip and matches a less noisy UI.
79-79: LGTM: Conditional NftData render.With the object-guard above, this will avoid rendering junk rows for primitive data.
crates/wallet/storage_sqlite/src/reader.rs (1)
954-957: Deterministic ordering added (newest-first) — LGTM. Confirmed callers: crates/wallet/sdk/src/apis/stealth_outputs.rs::utxos_get_many (used by applications/tari_walletd/src/handlers/stealth_utxos.rs). diesel schema (crates/wallet/storage_sqlite/src/schema.rs) shows stealth_outputs (id) is the PK and the migration creates the table/indexes — ordering by stealth_outputs::id.desc() corresponds to newest-first and should leverage the PK index; avoid returning very large/unpaged result sets.applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/Tokens.tsx (1)
153-165: Conditional mount of Send dialog is goodMounting only when a resource is selected avoids unnecessary renders and state churn. Looks good.
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/steps/FormStep.tsx (2)
108-110: Formatting change is reasonableUsing minimumFractionDigits: 0 with divisibility as the cap aligns with divisibility-based display.
207-211: Correct: pass divisibility and symbol to formatterThis aligns helper usage with resource-specific divisibility/symbol.
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx (2)
174-177: Nice defensive check in estimateFeeEarly-exit if the balance entry is missing prevents bad requests.
311-311: LGTM: pass exact divisibilityUsing the exact resource divisibility improves formatting/validation downstream.
| function NftCard({ nft }: { nft: NonFungibleToken }) { | ||
| const mutableData = convertCborValue(nft.mutable_data); | ||
| const data = convertCborValue(nft.data); | ||
| const data = convertCborValue(nft.data) as Record<string, any> | undefined; |
There was a problem hiding this comment.
Guard against non-object NFT data before rendering.
convertCborValue(nft.data) can return primitives/arrays; passing truthy non-objects into NftData will render character-by-character rows. Narrow to plain objects first.
Apply this diff:
- const data = convertCborValue(nft.data) as Record<string, any> | undefined;
+ const rawData = convertCborValue(nft.data);
+ const data =
+ rawData && typeof rawData === "object" && !Array.isArray(rawData)
+ ? (rawData as Record<string, unknown>)
+ : undefined;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const data = convertCborValue(nft.data) as Record<string, any> | undefined; | |
| const rawData = convertCborValue(nft.data); | |
| const data = | |
| rawData && typeof rawData === "object" && !Array.isArray(rawData) | |
| ? (rawData as Record<string, unknown>) | |
| : undefined; |
🤖 Prompt for AI Agents
In
applications/tari_walletd/web_ui/src/routes/AssetVault/NFTs/components/NftParts.tsx
around line 36, convertCborValue(nft.data) can return primitives or arrays which
then render incorrectly; instead of blindly casting, first check the result is a
plain object (data !== null && typeof data === 'object' && !Array.isArray(data))
and only then assign/cast it to Record<string, any>; otherwise set data to
undefined (or skip rendering NftData) so NftData receives only plain objects.
| 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, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
🧩 Analysis chain
Avoid precision loss; compute atomic amount via BigInt; remove XTR fallback
Using Number with Math.pow can lose precision for high divisibility tokens. Convert the decimal string to atomic units using BigInt and pass as a string (if API supports it). Also, the XTR fallback is unnecessary/inconsistent given the earlier guard.
- let amount = Math.floor((parseFloat(transferFormState.amount) || 0) * Math.pow(10, balanceEntry.divisibility));
+ const amount = decimalToAtomic(transferFormState.amount, balanceEntry.divisibility);
// Create transfer object with current form state
const currentTransfer = {
account: substateIdToString(account.component_address),
- amount,
- resource_address: props.resource_address || XTR,
+ amount, // string to avoid precision loss
+ resource_address: props.resource_address!,Helper to add (outside this hunk), e.g. near the top of the file:
function decimalToAtomic(amountStr: string, divisibility: number): string {
const sanitized = (amountStr || "").trim();
if (!sanitized) return "0";
const [wholeRaw, fracRaw = ""] = sanitized.split(".");
const whole = wholeRaw.replace(/\D/g, "") || "0";
const fracClean = fracRaw.replace(/\D/g, "");
const frac = fracClean.slice(0, Math.max(0, divisibility)).padEnd(Math.max(0, divisibility), "0");
const base = BigInt(10) ** BigInt(Math.max(0, divisibility));
const atomic = BigInt(whole || "0") * base + BigInt(frac || "0");
return atomic.toString();
}To verify the request type accepts a string amount, run:
🏁 Script executed:
#!/bin/bash
# Locate the accounts transfer hook/type and its request shape
rg -nC3 'useAccountsTransfer\(|accountsTransfer\(|Transfer' --type=ts --type=tsx
rg -nC3 '\bamount\b' --type=ts --type=tsxLength of output: 55
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Searching for SendMoney.tsx..."
files=$(find . -type f -name 'SendMoney.tsx' 2>/dev/null || true)
if [ -z "$files" ]; then
echo "SendMoney.tsx not found via find"
else
for f in $files; do
echo "== $f =="
sed -n '1,360p' "$f"
done
fi
echo
echo "--- Searching for transfer-related symbols and amount/resource_address usages ---"
rg -n --hidden -S 'useAccountsTransfer' || true
rg -n --hidden -S 'accountsTransfer' || true
rg -n --hidden -S '\btransfer\b' || true
rg -n --hidden -S 'resource_address' || true
rg -n --hidden -S '\bamount\b' || true
rg -n --hidden -S '\bXTR\b' || trueLength of output: 270100
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== bindings/src/types/wallet-daemon-client/AccountsTransferRequest.ts =="
sed -n '1,240p' bindings/src/types/wallet-daemon-client/AccountsTransferRequest.ts || true
echo
echo "== applications/tari_walletd/web_ui/src/services/api/hooks/useAccounts.ts =="
sed -n '1,420p' applications/tari_walletd/web_ui/src/services/api/hooks/useAccounts.ts || true
echo
echo "== clients/javascript/wallet_daemon_client/src/index.ts (accountsTransfer) =="
sed -n '1,420p' clients/javascript/wallet_daemon_client/src/index.ts || true
echo
echo "== bindings/src/helpers/consts.ts =="
sed -n '1,240p' bindings/src/helpers/consts.ts || trueLength of output: 23867
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for Amount type in bindings =="
rg -n --hidden -S '(^|\s)(export )?(type|interface) Amount\b' bindings || true
echo
echo "== bindings/src/types/Amount.ts =="
sed -n '1,240p' bindings/src/types/Amount.ts || true
echo
echo "== show generated bindings index to confirm path imports =="
sed -n '1,240p' bindings/src/index.ts || trueLength of output: 7836
Convert decimal input to atomic units with BigInt, send as string, and remove XTR fallback (update hook types)
- Rationale: Number + Math.pow loses precision for high-divisibility tokens; convert user decimal string -> atomic units with BigInt (decimalToAtomic) and send that atomic value as a string (bindings allow string amounts: bindings/src/types/Amount.ts -> export type Amount = string | number).
- Change SendMoney.tsx: replace Math.floor((parseFloat(... ) || 0) * Math.pow(10, balanceEntry.divisibility)) in both estimateFee and handleConfirm with decimalToAtomic(transferFormState.amount, balanceEntry.divisibility). Add the decimalToAtomic helper (returns string) and pass amount as that string. Remove the resource_address fallback (use props.resource_address! instead of props.resource_address || XTR). Location: applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx (estimateFee + handleConfirm).
- Change hook typing: update TransferParams.amount in applications/tari_walletd/web_ui/src/services/api/hooks/useAccounts.ts from number -> Amount (imported from @tari-project/typescript-bindings) or at least string | number so passing a string does not break TypeScript; confirm and update any other callers that rely on TransferParams.
🤖 Prompt for AI Agents
In
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx
around lines 182-187 (and the matching spots in estimateFee and handleConfirm),
replace the Math.floor(parseFloat(...) * Math.pow(10, ...)) logic with a
decimalToAtomic helper that converts the decimal string and divisibility into
atomic units as a string, pass that string as the transfer amount, and remove
the XTR fallback by using props.resource_address! instead of
props.resource_address || XTR; additionally add the decimalToAtomic helper
(returns string) to this file or a shared util and ensure both estimateFee and
handleConfirm use it. Update the TransferParams.amount type in
applications/tari_walletd/web_ui/src/services/api/hooks/useAccounts.ts to use
Amount (imported from @tari-project/typescript-bindings) or at minimum string |
number so the string atomic amount is accepted, and update any callers of
TransferParams to comply with the new type.
| 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, | ||
| }; | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Apply the same BigInt amount conversion in confirm path
Mirror the safe atomic amount conversion in the submit path for consistency.
- let amount = Math.floor((parseFloat(transferFormState.amount) || 0) * Math.pow(10, balanceEntry.divisibility));
- const transfer = {
- account: substateIdToString(account.component_address),
- amount,
+ const amount = decimalToAtomic(transferFormState.amount, balanceEntry.divisibility);
+ const transfer = {
+ account: substateIdToString(account.component_address),
+ amount, // string
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,
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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, | |
| }; | |
| const amount = decimalToAtomic(transferFormState.amount, balanceEntry.divisibility); | |
| const transfer = { | |
| account: substateIdToString(account.component_address), | |
| amount, // string | |
| 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, | |
| }; |
🤖 Prompt for AI Agents
In
applications/tari_walletd/web_ui/src/routes/AssetVault/Tokens/components/SendMoney.tsx
around lines 246 to 257, the confirm path constructs transfer.amount as a Number
using Math.floor which is inconsistent with the submit path; change the atomic
amount computation to produce a BigInt the same way the submit path does by
parsing transferFormState.amount, multiplying by 10**balanceEntry.divisibility,
flooring, and wrapping the result with BigInt (e.g. const amount =
BigInt(Math.floor((parseFloat(...) || 0) * Math.pow(10, divisibility)))); then
assign that BigInt to transfer.amount and ensure types align with the transfer
object.
Description
fix(wallet/webui): honour resource divisibility in UI
Display NFT metadata (originalOwner is only metadata that was added for the testnet faucet NFT)
Remove "Active" chip from NFTs (could be confusing)
Motivation and Context
Use correct divisibility and token symbol when formatting currency
How Has This Been Tested?
What process can a PR reviewer use to test or verify this change?
Breaking Changes
Summary by CodeRabbit
New Features
Improvements
Bug Fixes