feat(epic-2): implement WhaleActivityFeed component with mock data

- Add WhaleActivityFeed.tsx for displaying whale transactions (>0K)
- Features: real-time feed, filter by watchlist/smart money, track wallet
- Add mock whale transaction data with 7 sample transactions
- Support for buy/sell indicators, smart money badges, explorer links
- Implements Story 2.2: Whale Activity Tracker from Epic 2
This commit is contained in:
API Test Bot 2026-02-04 02:31:36 +07:00
parent e4d020799b
commit db22cd4a64
2 changed files with 373 additions and 0 deletions

View file

@ -7,6 +7,7 @@ import type { TokenData } from "../context/PageContextProvider";
import type { WatchlistToken, WatchlistAlert } from "../crypto/WatchlistPanel";
import type { SafetyFactor } from "../crypto/SafetyScoreDisplay";
import type { AlertConfig } from "../crypto/AlertConfigModal";
import type { WhaleTransaction } from "../whale/WhaleActivityFeed";
// ============================================
// MOCK TOKEN DATA (DexScreener)
@ -196,6 +197,111 @@ export const MOCK_WATCHLIST_ALERTS: WatchlistAlert[] = [
},
];
// ============================================
// MOCK WHALE TRANSACTIONS
// ============================================
export const MOCK_WHALE_TRANSACTIONS: WhaleTransaction[] = [
{
id: "whale-1",
tokenSymbol: "BULLA",
tokenName: "Bulla Token",
chain: "solana",
type: "buy",
amountUSD: 100000,
amountTokens: "8.1B",
walletAddress: "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
txHash: "5Kn8WqXZKqYqKqYqKqYqKqYqKqYqKqYqKqYqKqYqKqYq",
timestamp: new Date(Date.now() - 1000 * 60 * 2), // 2 mins ago
isSmartMoney: true,
isInWatchlist: true,
},
{
id: "whale-2",
tokenSymbol: "BONK",
tokenName: "Bonk",
chain: "solana",
type: "sell",
amountUSD: 50000,
amountTokens: "2.3B",
walletAddress: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
txHash: "3Hn7WpXZKpYpKpYpKpYpKpYpKpYpKpYpKpYpKpYpKpYp",
timestamp: new Date(Date.now() - 1000 * 60 * 5), // 5 mins ago
isSmartMoney: false,
isInWatchlist: true,
},
{
id: "whale-3",
tokenSymbol: "PEPE",
tokenName: "Pepe",
chain: "ethereum",
type: "buy",
amountUSD: 250000,
amountTokens: "23B",
walletAddress: "0x6982508145454Ce325dDbE47a25d4ec3d2311933",
txHash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
timestamp: new Date(Date.now() - 1000 * 60 * 10), // 10 mins ago
isSmartMoney: true,
isInWatchlist: true,
},
{
id: "whale-4",
tokenSymbol: "WIF",
tokenName: "dogwifhat",
chain: "solana",
type: "buy",
amountUSD: 75000,
amountTokens: "30.6K",
walletAddress: "EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm",
txHash: "4Jm6VoXYJoXoJoXoJoXoJoXoJoXoJoXoJoXoJoXoJoXo",
timestamp: new Date(Date.now() - 1000 * 60 * 15), // 15 mins ago
isSmartMoney: false,
isInWatchlist: true,
},
{
id: "whale-5",
tokenSymbol: "DEGEN",
tokenName: "Degen",
chain: "base",
type: "sell",
amountUSD: 35000,
amountTokens: "2.2M",
walletAddress: "0x4ed4E862860beD51a9570b96d89aF5E1B0Efefed",
txHash: "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
timestamp: new Date(Date.now() - 1000 * 60 * 20), // 20 mins ago
isSmartMoney: false,
isInWatchlist: true,
},
{
id: "whale-6",
tokenSymbol: "SOL",
tokenName: "Solana",
chain: "solana",
type: "buy",
amountUSD: 500000,
amountTokens: "5K",
walletAddress: "So11111111111111111111111111111111111111112",
txHash: "6Lp7XqYZLqZqLqZqLqZqLqZqLqZqLqZqLqZqLqZqLqZq",
timestamp: new Date(Date.now() - 1000 * 60 * 30), // 30 mins ago
isSmartMoney: true,
isInWatchlist: false,
},
{
id: "whale-7",
tokenSymbol: "MATIC",
tokenName: "Polygon",
chain: "ethereum",
type: "buy",
amountUSD: 150000,
amountTokens: "200K",
walletAddress: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0",
txHash: "0xfedcba0987654321fedcba0987654321fedcba0987654321fedcba0987654321",
timestamp: new Date(Date.now() - 1000 * 60 * 45), // 45 mins ago
isSmartMoney: false,
isInWatchlist: false,
},
];
// ============================================
// MOCK ALERT CONFIGS
// ============================================

View file

@ -0,0 +1,267 @@
import { useState } from "react";
import { cn } from "~/lib/utils";
import {
TrendingUp,
TrendingDown,
ExternalLink,
Filter,
Star,
Clock,
DollarSign,
Wallet,
} from "lucide-react";
import { Button } from "@/routes/ui/button";
import { ChainIcon } from "../components/shared/ChainIcon";
export interface WhaleTransaction {
/** Transaction ID */
id: string;
/** Token symbol */
tokenSymbol: string;
/** Token name */
tokenName?: string;
/** Blockchain chain */
chain: string;
/** Transaction type */
type: "buy" | "sell";
/** Amount in USD */
amountUSD: number;
/** Amount in tokens */
amountTokens: string;
/** Wallet address */
walletAddress: string;
/** Transaction hash */
txHash: string;
/** When the transaction occurred */
timestamp: Date;
/** Whether this is a smart money wallet */
isSmartMoney?: boolean;
/** Whether this token is in user's watchlist */
isInWatchlist?: boolean;
}
export interface WhaleActivityFeedProps {
/** List of whale transactions */
transactions: WhaleTransaction[];
/** Callback when transaction is clicked */
onTransactionClick?: (tx: WhaleTransaction) => void;
/** Callback when "Track Wallet" is clicked */
onTrackWallet?: (walletAddress: string) => void;
/** Callback when "View on Explorer" is clicked */
onViewExplorer?: (txHash: string, chain: string) => void;
/** Additional class names */
className?: string;
}
/**
* WhaleActivityFeed - Display whale transactions (large buys/sells >$10K)
*
* Features:
* - Real-time feed of large transactions
* - Filter by watchlist tokens or all tokens
* - Smart money wallet indicators
* - Quick links to block explorer
* - Track wallet functionality
*/
export function WhaleActivityFeed({
transactions,
onTransactionClick,
onTrackWallet,
onViewExplorer,
className,
}: WhaleActivityFeedProps) {
const [filter, setFilter] = useState<"all" | "watchlist" | "smart_money">("all");
// Filter transactions based on selected filter
const filteredTransactions = transactions.filter((tx) => {
if (filter === "watchlist") return tx.isInWatchlist;
if (filter === "smart_money") return tx.isSmartMoney;
return true;
});
const formatTimeAgo = (date: Date) => {
const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
if (seconds < 60) return `${seconds}s ago`;
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
return `${Math.floor(hours / 24)}d ago`;
};
const formatAmount = (amount: number) => {
if (amount >= 1000000) return `$${(amount / 1000000).toFixed(2)}M`;
if (amount >= 1000) return `$${(amount / 1000).toFixed(1)}K`;
return `$${amount.toFixed(0)}`;
};
const shortenAddress = (address: string) => {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
};
return (
<div className={cn("flex flex-col h-full", className)}>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b">
<div className="flex items-center gap-2">
<div className="text-2xl">🐋</div>
<div>
<h2 className="font-semibold">Whale Activity</h2>
<p className="text-xs text-muted-foreground">
{filteredTransactions.length} transactions
</p>
</div>
</div>
</div>
{/* Filter tabs */}
<div className="flex gap-1 p-2 border-b bg-muted/30">
<Button
variant={filter === "all" ? "default" : "ghost"}
size="sm"
onClick={() => setFilter("all")}
className="flex-1"
>
All
</Button>
<Button
variant={filter === "watchlist" ? "default" : "ghost"}
size="sm"
onClick={() => setFilter("watchlist")}
className="flex-1"
>
<Star className="h-3 w-3 mr-1" />
Watchlist
</Button>
<Button
variant={filter === "smart_money" ? "default" : "ghost"}
size="sm"
onClick={() => setFilter("smart_money")}
className="flex-1"
>
Smart Money
</Button>
</div>
{/* Transaction feed */}
<div className="flex-1 overflow-y-auto">
{filteredTransactions.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full p-8 text-center">
<div className="text-4xl mb-2">🐋</div>
<p className="text-sm text-muted-foreground">
No whale activity detected yet
</p>
<p className="text-xs text-muted-foreground mt-1">
Large transactions (&gt;$10K) will appear here
</p>
</div>
) : (
<div className="divide-y">
{filteredTransactions.map((tx) => (
<div
key={tx.id}
className="p-4 hover:bg-muted/50 transition-colors cursor-pointer"
onClick={() => onTransactionClick?.(tx)}
>
{/* Time and smart money badge */}
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<Clock className="h-3 w-3" />
{formatTimeAgo(tx.timestamp)}
</div>
{tx.isSmartMoney && (
<div className="flex items-center gap-1 px-2 py-0.5 bg-purple-500/10 text-purple-600 dark:text-purple-400 rounded-full text-xs font-medium">
Smart Money
</div>
)}
</div>
{/* Transaction type and amount */}
<div className="flex items-center gap-2 mb-2">
{tx.type === "buy" ? (
<div className="flex items-center gap-1 text-green-600 dark:text-green-400 font-semibold">
<TrendingUp className="h-4 w-4" />
BUY
</div>
) : (
<div className="flex items-center gap-1 text-red-600 dark:text-red-400 font-semibold">
<TrendingDown className="h-4 w-4" />
SELL
</div>
)}
<span className="font-bold text-lg">
{formatAmount(tx.amountUSD)}
</span>
<span className="text-sm font-medium">
{tx.tokenSymbol}
</span>
<ChainIcon chain={tx.chain} size="sm" />
</div>
{/* Token amount */}
<div className="text-xs text-muted-foreground mb-2">
Amount: {tx.amountTokens} tokens
</div>
{/* Wallet address */}
<div className="flex items-center gap-2 mb-3">
<Wallet className="h-3 w-3 text-muted-foreground" />
<code className="text-xs bg-muted px-2 py-0.5 rounded">
{shortenAddress(tx.walletAddress)}
</code>
<button
className="text-xs text-primary hover:underline"
onClick={(e) => {
e.stopPropagation();
navigator.clipboard.writeText(tx.walletAddress);
}}
>
Copy
</button>
</div>
{/* Action buttons */}
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
className="flex-1 h-8 text-xs"
onClick={(e) => {
e.stopPropagation();
onViewExplorer?.(tx.txHash, tx.chain);
}}
>
<ExternalLink className="h-3 w-3 mr-1" />
Explorer
</Button>
{!tx.isSmartMoney && (
<Button
variant="outline"
size="sm"
className="flex-1 h-8 text-xs"
onClick={(e) => {
e.stopPropagation();
onTrackWallet?.(tx.walletAddress);
}}
>
<Star className="h-3 w-3 mr-1" />
Track Wallet
</Button>
)}
</div>
</div>
))}
</div>
)}
</div>
{/* Footer info */}
<div className="border-t p-3 bg-muted/30">
<div className="flex items-center justify-between text-xs text-muted-foreground">
<span>Monitoring transactions &gt;$10K</span>
<span>Updates every 1 min</span>
</div>
</div>
</div>
);
}