diff --git a/src/app/profile/[address]/page.tsx b/src/app/profile/[address]/page.tsx
index d7aa6678..2108efc5 100644
--- a/src/app/profile/[address]/page.tsx
+++ b/src/app/profile/[address]/page.tsx
@@ -1441,13 +1441,24 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile
const totalValue = holdings?.reduce((sum, h) => sum + h.value, BigInt(0)) ?? BigInt(0);
const reserveDecimals = holdings && holdings.length > 0 ? holdings[0].reserveDecimals : 18;
- const bestPick = holdings && holdings.length > 0
- ? holdings.reduce((best, h) =>
- (h.priceChange ?? -Infinity) > (best.priceChange ?? -Infinity) ? h : best
- )
- : null;
const totalDonated = donationsGiven.reduce((sum, d) => sum + BigInt(d.amount), BigInt(0));
+ // Compute portfolio-level cost basis % change (only if all holdings have entry prices)
+ const portfolioCostPct = (() => {
+ if (!holdings || holdings.length === 0 || plotUsd == null) return null;
+ if (holdings.some(h => h.entryPrice === null || h.entryPrice <= 0)) return null;
+ let totalCurrentUsd = 0;
+ let totalCostUsd = 0;
+ for (const h of holdings) {
+ const currentPrice = Number(formatUnits(h.price, 18));
+ const balanceNum = Number(formatUnits(h.balance, 18));
+ totalCurrentUsd += currentPrice * balanceNum * plotUsd;
+ totalCostUsd += h.entryPrice! * balanceNum * plotUsd;
+ }
+ if (totalCostUsd === 0) return null;
+ return ((totalCurrentUsd - totalCostUsd) / totalCostUsd) * 100;
+ })();
+
return (
{/* Portfolio summary */}
@@ -1455,25 +1466,22 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile
<>
Portfolio
-
-
-
{formatPrice(formatUnits(totalValue, reserveDecimals))}
-
{RESERVE_LABEL}
-
+
-
{plotUsd ? formatUsdValue(Number(formatUnits(totalValue, reserveDecimals)) * plotUsd) : "—"}
-
USD
+
+ {plotUsd ? formatUsdValue(Number(formatUnits(totalValue, reserveDecimals)) * plotUsd) : "—"}
+ {portfolioCostPct !== null && (
+ = 0 ? "text-accent" : "text-error"}`}>
+ {portfolioCostPct >= 0 ? "+" : ""}{portfolioCostPct.toFixed(1)}%
+
+ )}
+
+
Value
{holdings!.length}
Holdings
-
-
= 0 ? "text-accent" : "text-error") : "text-foreground"}`}>
- {bestPick && bestPick.priceChange !== null ? `${bestPick.priceChange >= 0 ? "+" : ""}${bestPick.priceChange.toFixed(1)}%` : "—"}
-
-
Best 24h
-
>
@@ -1527,11 +1535,16 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile
{plotUsd ? formatUsdValue(Number(formatUnits(h.value, h.reserveDecimals)) * plotUsd) : "—"}
- {h.priceChange !== null && (
- = 0 ? "text-accent" : "text-error"}`}>
- {h.priceChange >= 0 ? "+" : ""}{h.priceChange.toFixed(1)}%
-
- )}
+ {(() => {
+ if (h.entryPrice == null || h.entryPrice <= 0 || plotUsd == null) return null;
+ const currentPrice = Number(formatUnits(h.price, 18));
+ const costPct = ((currentPrice - h.entryPrice) / h.entryPrice) * 100;
+ return (
+ = 0 ? "text-accent" : "text-error"}`}>
+ {costPct >= 0 ? "+" : ""}{costPct.toFixed(1)}%
+
+ );
+ })()}
Value
@@ -1540,33 +1553,9 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile
{formatCompact(Number(formatUnits(h.balance, 18)))}
Balance
- {/* PnL */}
-
- {h.entryPrice !== null && h.entryPrice > 0 && plotUsd != null ? (() => {
- const currentPrice = Number(formatUnits(h.price, 18));
- const balanceNum = Number(formatUnits(h.balance, 18));
- const pnlUsd = (currentPrice - h.entryPrice) * balanceNum * plotUsd;
- const pnlPct = ((currentPrice - h.entryPrice) / h.entryPrice) * 100;
- const isPositive = pnlUsd >= 0;
- return (
-
- {isPositive ? "+" : "-"}{formatUsdValue(Math.abs(pnlUsd))}
- {isPositive ? "+" : ""}{pnlPct.toFixed(1)}%
-
- );
- })() : (
-
—
- )}
-
PnL
-
- {/* First Traded */}
-
-
- {h.firstTraded ? new Date(h.firstTraded).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }) : "—"}
-
-
First Traded
-
+ {/* Recent transactions */}
+
@@ -1579,6 +1568,48 @@ function PortfolioTab({ address, isOwnProfile }: { address: string; isOwnProfile
);
}
+// ---------------------------------------------------------------------------
+// Holding Recent Trades — last 5 transactions for a specific story token
+// ---------------------------------------------------------------------------
+
+function HoldingRecentTrades({ address, storylineId, plotUsd }: { address: string; storylineId: number; plotUsd?: number | null }) {
+ const { data: trades, isLoading } = useQuery({
+ queryKey: ["holding-recent-trades", address, storylineId],
+ queryFn: async () => {
+ if (!supabase) return [];
+ const { data } = await supabase
+ .from("trade_history")
+ .select("event_type, reserve_amount, block_timestamp")
+ .eq("user_address", address)
+ .eq("storyline_id", storylineId)
+ .eq("contract_address", MCV2_BOND.toLowerCase())
+ .order("block_timestamp", { ascending: false })
+ .limit(5);
+ return data ?? [];
+ },
+ staleTime: 60000,
+ });
+
+ if (isLoading || !trades || trades.length === 0) return null;
+
+ return (
+
+ {trades.map((t, i) => {
+ const isBuy = t.event_type === "mint";
+ const date = new Date(t.block_timestamp).toLocaleDateString("en-US", { month: "short", day: "numeric" });
+ const amount = plotUsd != null ? formatUsdValue(t.reserve_amount * plotUsd) : `${formatPrice(t.reserve_amount)} ${RESERVE_LABEL}`;
+ return (
+
+ {isBuy ? "Buy" : "Sell"}
+ {amount}
+ {date}
+
+ );
+ })}
+
+ );
+}
+
// ---------------------------------------------------------------------------
// Portfolio Trading History — paginated trades
// ---------------------------------------------------------------------------