From e36da4e2831e2115ec9a44e4932f26c0a72c8789 Mon Sep 17 00:00:00 2001 From: Ehab Khedr <73967887+EKF0@users.noreply.github.com> Date: Sat, 16 May 2026 19:34:52 +0300 Subject: [PATCH] feat: portfolio reconciliation with real on-chain balances (SOL2-05) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created lib/solana/balances.ts: fetches native SOL + SPL tokens via getBalance + getParsedTokenAccountsByOwner, enriches with catalog metadata, filters dust accounts - Created lib/stores/balance-store.ts: Zustand refreshCounter for cross-component post-transaction refresh signals - Created hooks/use-wallet-balances.ts: React hook with auto-fetch on wallet connect/disconnect and Zustand counter subscription - Replaced all mock data in dashboard components: - holdings-table.tsx: real token list with loading/empty states - net-worth.tsx: real totalValueUsd (day change deferred to SOL6) - asset-allocation.tsx: dynamic doughnut chart from actual holdings - deposit-modal.tsx: calls triggerRefresh() after confirmed session - bag-card.tsx: shows Your Position with actual vs. target allocation per asset and drift indicators - USD pricing uses temporary hardcoded price map — real feed in SOL3 --- components/bags/bag-card.tsx | 53 +++++++ components/bags/deposit-modal.tsx | 5 + components/dashboard/asset-allocation.tsx | 69 ++++++--- components/dashboard/holdings-table.tsx | 118 +++++++------- components/dashboard/net-worth.tsx | 51 ++++-- docs/production-readiness-plan.csv | 2 +- docs/progress.md | 14 ++ hooks/use-wallet-balances.ts | 129 ++++++++++++++++ lib/solana/balances.ts | 146 ++++++++++++++++++ lib/stores/balance-store.ts | 21 +++ ...conciliation-after-swaps-and-rebalances.md | 47 ++++++ 11 files changed, 567 insertions(+), 88 deletions(-) create mode 100644 hooks/use-wallet-balances.ts create mode 100644 lib/solana/balances.ts create mode 100644 lib/stores/balance-store.ts create mode 100644 tasks/sol2-05-add-portfolio-reconciliation-after-swaps-and-rebalances.md diff --git a/components/bags/bag-card.tsx b/components/bags/bag-card.tsx index 4713711..52fdc65 100644 --- a/components/bags/bag-card.tsx +++ b/components/bags/bag-card.tsx @@ -4,11 +4,34 @@ import { useState } from 'react'; import { DepositModal } from './deposit-modal'; import { ArrowRight, RotateCw } from 'lucide-react'; import { allocationLabel, type SmartBagTemplate } from '@/lib/smart-bags/session-engine'; +import { useWalletBalances } from '@/hooks/use-wallet-balances'; +import { useWallet } from '@solana/wallet-adapter-react'; export function BagCard(bag: SmartBagTemplate) { const [isDepositOpen, setIsDepositOpen] = useState(false); + const { connected } = useWallet(); + const { balances } = useWalletBalances(); const { title, description, metricLabel, metricValue, risk, assets, strategy, maxSlippageBps } = bag; + // Compute user position for this bag's target mints + const position = connected && balances.length > 0 ? (() => { + const held = assets + .map((asset) => { + const match = balances.find((b) => b.mint === asset.mint); + return { + symbol: asset.symbol, + targetBps: asset.allocationBps, + valueUsd: match?.valueUsd ?? 0, + }; + }) + .filter((item) => item.valueUsd > 0); + + const totalValue = held.reduce((sum, h) => sum + h.valueUsd, 0); + if (totalValue < 0.01) return null; + + return { held, totalValue }; + })() : null; + return ( <>
No assets to display
+| Asset | -Balance | -
-
- Value (USD)
-
- |
- Network | -||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
-
-
-
- {asset.symbol[0]}
-
-
-
- {asset.symbol}
- {asset.name}
- |
- - {asset.balance} - | -- ${asset.valueUSD.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} - | -
-
-
- {asset.chain}
+
+ {isLoading && balances.length === 0 ? (
+
+
+ ) : balances.length === 0 ? (
+
+ No holdings found for this wallet.
+
+ ) : (
+
+
+
+
+ {balances.map((asset) => (
+
+ |
+
+
+
+ {asset.icon}
+
+
+
+ {asset.symbol}
+ {asset.name}
+
+ {asset.balanceUi.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 6 })}
+ |
+
+ ${asset.valueUsd.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
+ |
+
+ |
+
+
+ Solana
+
+ |
Total Net Worth
- +${dayChange.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} today + {/* Day change requires historical snapshots — available after SOL6 */} +
+ Daily change available after history tracking is enabled