diff --git a/backend/app/core/config.py b/backend/app/core/config.py
index fb8f56e..50823fb 100644
--- a/backend/app/core/config.py
+++ b/backend/app/core/config.py
@@ -7,7 +7,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
# title, /api/health (which the frontend reads to show it in Settings), and
# embedded in exported backups so an old file can be told apart from a
# current one.
-APP_VERSION = "1.1.4"
+APP_VERSION = "1.1.5"
class Settings(BaseSettings):
diff --git a/frontend/package.json b/frontend/package.json
index 161e9bf..6018dad 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,7 +1,7 @@
{
"name": "aurum-frontend",
"private": true,
- "version": "1.1.4",
+ "version": "1.1.5",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/frontend/src/components/crypto/CryptoAllocationBody.tsx b/frontend/src/components/crypto/CryptoAllocationBody.tsx
index 73ff61e..26fa716 100644
--- a/frontend/src/components/crypto/CryptoAllocationBody.tsx
+++ b/frontend/src/components/crypto/CryptoAllocationBody.tsx
@@ -1,5 +1,5 @@
import { Cell, Pie, PieChart, ResponsiveContainer, Tooltip } from "recharts";
-import { formatCurrency, maskAmount } from "@/lib/format";
+import { formatCryptoAmount, maskAmount } from "@/lib/format";
import { useTranslation } from "@/lib/i18n";
import type { CryptoHolding } from "@/types";
@@ -65,7 +65,7 @@ function DonutTooltip({
{slice.key === "other" ? otherLabel : slice.name}
- {maskAmount(formatCurrency(slice.amount), hidden)} · {slice.percent.toFixed(1)}%
+ {maskAmount(formatCryptoAmount(slice.amount), hidden)} · {slice.percent.toFixed(1)}%
);
@@ -132,7 +132,7 @@ export function CryptoAllocationBody({ holdings, isLoading, hidden }: CryptoAllo
{slice.percent.toFixed(1)}%
|
- {maskAmount(formatCurrency(slice.amount), hidden)} |
+ {maskAmount(formatCryptoAmount(slice.amount), hidden)} |
))}
diff --git a/frontend/src/components/crypto/CryptoHoldingsTable.tsx b/frontend/src/components/crypto/CryptoHoldingsTable.tsx
index 0c84557..cebb983 100644
--- a/frontend/src/components/crypto/CryptoHoldingsTable.tsx
+++ b/frontend/src/components/crypto/CryptoHoldingsTable.tsx
@@ -1,5 +1,5 @@
import { Bitcoin, Plus, Trash2 } from "lucide-react";
-import { formatCurrency, maskAmount } from "@/lib/format";
+import { formatCryptoAmount, maskAmount } from "@/lib/format";
import { useTranslation } from "@/lib/i18n";
import type { CryptoHolding, CryptoPortfolio } from "@/types";
@@ -106,7 +106,7 @@ export function CryptoHoldingsTable({
{holding.current_price !== null
- ? maskAmount(formatCurrency(holding.current_price), hidden)
+ ? maskAmount(formatCryptoAmount(holding.current_price), hidden)
: t("crypto.pendingPrice")}
|
@@ -120,20 +120,20 @@ export function CryptoHoldingsTable({
|
- {holding.value !== null ? maskAmount(formatCurrency(holding.value), hidden) : t("crypto.pendingPrice")}
+ {holding.value !== null ? maskAmount(formatCryptoAmount(holding.value), hidden) : t("crypto.pendingPrice")}
{maskAmount(`${Number(holding.quantity)} ${holding.symbol}`, hidden)}
|
- {holding.avg_buy_price !== null ? maskAmount(formatCurrency(holding.avg_buy_price), hidden) : "—"}
+ {holding.avg_buy_price !== null ? maskAmount(formatCryptoAmount(holding.avg_buy_price), hidden) : "—"}
|
{holding.profit_loss !== null && holding.profit_loss_percent !== null ? (
<>
- {maskAmount(`${sign}${formatCurrency(holding.profit_loss)}`, hidden)}
+ {maskAmount(`${sign}${formatCryptoAmount(holding.profit_loss)}`, hidden)}
{sign}
diff --git a/frontend/src/components/crypto/CryptoTransactionHistoryModal.tsx b/frontend/src/components/crypto/CryptoTransactionHistoryModal.tsx
index 3158281..793bacf 100644
--- a/frontend/src/components/crypto/CryptoTransactionHistoryModal.tsx
+++ b/frontend/src/components/crypto/CryptoTransactionHistoryModal.tsx
@@ -1,7 +1,7 @@
import { Pencil, Trash2 } from "lucide-react";
import { Dialog } from "@/components/ui/Dialog";
import { useCryptoTransactions, useDeleteCryptoTransaction } from "@/hooks/useCrypto";
-import { formatCurrency, formatTransactionDate, maskAmount } from "@/lib/format";
+import { formatCryptoAmount, formatTransactionDate, maskAmount } from "@/lib/format";
import { useTranslation } from "@/lib/i18n";
import type { CryptoHolding, CryptoTransaction } from "@/types";
@@ -47,7 +47,7 @@ export function CryptoTransactionHistoryModal({ open, onClose, holding, hidden,
- {maskAmount(`${Number(tx.quantity)} ${holding.symbol} · ${formatCurrency(tx.price_per_unit)}`, hidden)}
+ {maskAmount(`${Number(tx.quantity)} ${holding.symbol} · ${formatCryptoAmount(tx.price_per_unit)}`, hidden)}
{formatTransactionDate(tx.date, true)}
diff --git a/frontend/src/lib/format.ts b/frontend/src/lib/format.ts
index 5db7013..ed9035e 100644
--- a/frontend/src/lib/format.ts
+++ b/frontend/src/lib/format.ts
@@ -17,6 +17,29 @@ export function formatCurrency(amount: number | string, currency: string = getCu
}).format(value);
}
+/** Same currency formatting as formatCurrency, but scales decimal precision
+ * down to the value's own magnitude instead of always rounding to whole
+ * units — a low-cap memecoin can genuinely price at $0.000000006894, and
+ * formatCurrency's fixed 0 fraction digits would render that as "$0",
+ * indistinguishable from actually being worthless. Values >= 1 still show
+ * just 2 decimals (a coin price doesn't need more than cents once it's
+ * above a dollar). For the Crypto tab's per-coin price/holdings/avg-buy-price
+ * cells and per-transaction price — anywhere a single coin's own value
+ * needs to be told apart from zero, not just a portfolio-wide total. */
+export function formatCryptoAmount(amount: number | string, currency: string = getCurrency()): string {
+ const value = typeof amount === "string" ? Number(amount) : amount;
+ const abs = Math.abs(value);
+ const maximumFractionDigits =
+ abs === 0 || abs >= 1
+ ? 2
+ : // Leading zeros right after the decimal point before the first
+ // significant digit, plus 4 more digits of real precision beyond
+ // that — e.g. 0.000000006894 has 8 leading zeros, so this shows
+ // 12 decimal places, landing exactly on "6894" and nothing more.
+ Math.min(20, Math.max(0, -Math.floor(Math.log10(abs)) - 1) + 4);
+ return new Intl.NumberFormat(getIntlLocale(), { style: "currency", currency, maximumFractionDigits }).format(value);
+}
+
export function formatSignedCurrency(amount: number | string, currency: string = getCurrency()): string {
const value = typeof amount === "string" ? Number(amount) : amount;
const sign = value > 0 ? "+" : "";
|