✅ Completed:
- Core expansion refactor (~700 lines deleted)
- Zero-fetch expansions (data from cache)
- Multi-UTXO visualization (multiple edges per address)
- P2PK support (shows "P2PK Script" placeholders)
- Arrow buttons (expand leftmost/rightmost only)
- Cache optimized (30 days TXs, 1 day addresses)
- Console logs cleaned up
- Address expansion for newly-added addresses
Address with 300+ transactions:
- Currently: Expands to 20 TXs (hardcoded limit)
- User sees: "No more connections" even though 280+ remain
- Confusing and incomplete
Step 1: Initial Click
- Create TransactionCluster node
- Shows: "300 Spending TXs"
- Positioned where TXs would go
Step 2: Click Cluster
- Expands to first 20 TXs
- Positions them in vertical stack
- Adds "Load More" node at bottom
- "Load More" shows: "280 remaining"
Step 3: Click "Load More"
- Adds next 20 TXs below current ones
- Updates "Load More" node
- "Load More" shows: "260 remaining"
- Repeat until all shown
interface LoadMoreNodeData {
remainingCount: number;
address: string;
direction: 'receiving' | 'spending';
currentOffset: number; // Which batch we're on
onLoadMore: (nodeId: string) => void;
}
export const LoadMoreNode = ({ data }: NodeProps<LoadMoreNodeData>) => {
return (
<div className="load-more-node" onClick={() => data.onLoadMore(data.address)}>
<Download size={16} />
<span>Load 20 More</span>
<span className="count">{data.remainingCount} remaining</span>
</div>
);
};Update expandAddressNodeWithFetch:
// Check if result has many TXs (>10)
const totalTxCount = txListData.total || transactions.length;
if (totalTxCount > 10) {
console.log(`⚠️ Address has ${totalTxCount} TXs - creating cluster node`);
// Create cluster node instead of individual TXs
return {
nodes: [{
id: `tx-cluster-${address}-${direction}-${Date.now()}`,
type: 'transactionCluster',
position: {
x: addrNode.position.x + xOffset,
y: addrNode.position.y,
},
data: {
address,
direction,
totalCount: totalTxCount,
transactions: [], // Will be loaded progressively
label: `${totalTxCount} ${direction === 'receiving' ? 'Receiving' : 'Spending'} TXs`,
onExpand: () => {} // Will be set in App.tsx
},
}],
edges: [{
id: `e-cluster-${address}-${direction}`,
source: direction === 'receiving' ? clusterId : addrId,
target: direction === 'receiving' ? addrId : clusterId,
... // Styling
}]
};
}
// Otherwise, show TXs directly (< 10)Add handler for expanding transaction clusters:
const handleExpandCluster = useCallback(async (clusterId: string) => {
const cluster = nodes.find(n => n.id === clusterId);
if (!cluster || cluster.type !== 'transactionCluster') return;
const address = cluster.data.address;
const direction = cluster.data.direction;
const currentOffset = cluster.data.currentOffset || 0;
// Fetch next batch (20 TXs)
const response = await fetch(`${API_BASE_URL}/trace/address?...&skip=${currentOffset}&limit=20`);
// Add TXs below cluster
// If more remain, add/update "Load More" node
// Otherwise remove it
}, []);- Create LoadMoreNode component
- Update expandAddressNodeWithFetch to create cluster for >10 TXs
- Add cluster expansion handler in App.tsx
- Handle progressive loading (tracking offset)
- Style LoadMoreNode to be visually distinct
- Test with 300+ TX address
User clicks address with 300 TXs:
→ Cluster node appears: "300 Spending TXs"
User clicks cluster:
→ First 20 TXs appear
→ "Load More (280 remaining)" node appears
User clicks "Load More":
→ Next 20 TXs appear
→ "Load More (260 remaining)" updates
User clicks "Load More" 14 times total:
→ All 300 TXs visible
→ "Load More" node removed
Clean, progressive, user-controlled!