/**
* Wallet Analytics Module
* Advanced wallet analysis including whale tracking, scoring, and behavior patterns
*
* @author nich
* @github github.com/nirholas
* @license Apache-2.0
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { z } from "zod"
import { createPublicClient, http, formatUnits, parseAbi } from "viem"
import { mainnet, arbitrum, polygon, optimism } from "viem/chains"
// Known whale wallets (partial list for demo)
const knownWhales: Record<string, { label: string; type: string }> = {
"0x00000000219ab540356cBB839Cbe05303d7705Fa": { label: "ETH 2.0 Deposit Contract", type: "protocol" },
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2": { label: "WETH Contract", type: "protocol" },
"0xDA9dfA130Df4dE4673b89022EE50ff26f6EA73Cf": { label: "Kraken", type: "exchange" },
"0x267be1C1D684F78cb4F6a176C4911b741E4Ffdc0": { label: "Kraken", type: "exchange" },
"0x2B5634C42055806a59e9107ED44D43c426E58258": { label: "Kraken", type: "exchange" },
"0x8103683202aa8DA10536036EDef04CDd865C225E": { label: "Binance", type: "exchange" },
"0x28C6c06298d514Db089934071355E5743bf21d60": { label: "Binance", type: "exchange" },
"0x21a31Ee1afC51d94C2eFcCAa2092aD1028285549": { label: "Binance", type: "exchange" },
}
export function registerWalletAnalytics(server: McpServer) {
// Wallet scoring
server.tool(
"wallet_score",
"Calculate a comprehensive wallet score based on multiple factors",
{
address: z.string().describe("Wallet address to analyze"),
chain: z
.enum(["ethereum", "bsc", "polygon", "arbitrum", "solana"])
.default("ethereum")
.describe("Blockchain network"),
},
async ({ address, chain }) => {
// In production, fetch real data from blockchain APIs
// This provides the scoring framework and mock data
const metrics = {
// Age score (0-100)
walletAge: {
firstTxDate: "2021-03-15",
ageDays: 1400,
score: 85,
},
// Activity score (0-100)
activity: {
totalTxCount: 342,
last30DaysTx: 28,
avgTxPerMonth: 18,
score: 72,
},
// Balance diversity (0-100)
diversity: {
uniqueTokens: 15,
chainCount: 3,
nftCount: 8,
score: 68,
},
// DeFi engagement (0-100)
defi: {
protocolsUsed: ["Uniswap", "Aave", "Compound", "Curve"],
hasLiquidity: true,
hasStaking: true,
score: 82,
},
// Risk factors (lower is better)
risk: {
interactedWithFlagged: false,
hasHighRiskTokens: false,
unusualPatterns: false,
riskScore: 15,
},
}
// Calculate overall score
const overallScore = Math.round(
(metrics.walletAge.score * 0.2 +
metrics.activity.score * 0.25 +
metrics.diversity.score * 0.2 +
metrics.defi.score * 0.25 +
(100 - metrics.risk.riskScore) * 0.1)
)
const tier =
overallScore >= 80
? "Diamond"
: overallScore >= 65
? "Gold"
: overallScore >= 50
? "Silver"
: "Bronze"
return {
content: [
{
type: "text",
text: JSON.stringify(
{
address,
chain,
overallScore,
tier,
metrics,
recommendations: [
overallScore < 70 && "Increase DeFi protocol usage",
metrics.diversity.score < 60 && "Diversify token holdings",
metrics.activity.last30DaysTx < 10 && "Maintain regular activity",
].filter(Boolean),
},
null,
2
),
},
],
}
}
)
// Whale detection
server.tool(
"wallet_detect_whales",
"Detect whale wallets holding a specific token",
{
tokenAddress: z.string().describe("Token contract address"),
chain: z
.enum(["ethereum", "bsc", "polygon"])
.default("ethereum")
.describe("Blockchain network"),
minBalance: z.number().default(1000000).describe("Minimum balance in USD"),
},
async ({ tokenAddress, chain, minBalance }) => {
try {
// Get chain config
const chainConfig = chain === "ethereum" ? mainnet : chain === "polygon" ? polygon : arbitrum
const client = createPublicClient({
chain: chainConfig,
transport: http(),
})
// Query top token holders from Transfer events
// Note: This is a simplified approach - production should use indexed data like Alchemy/Moralis
const erc20Abi = parseAbi([
'function balanceOf(address owner) view returns (uint256)',
'function totalSupply() view returns (uint256)',
'function decimals() view returns (uint8)',
'event Transfer(address indexed from, address indexed to, uint256 value)'
])
// Get token info
const [totalSupply, decimals] = await Promise.all([
client.readContract({
address: tokenAddress as `0x${string}`,
abi: erc20Abi,
functionName: 'totalSupply',
}),
client.readContract({
address: tokenAddress as `0x${string}`,
abi: erc20Abi,
functionName: 'decimals',
}),
])
// Get known whale balances
const whaleAddresses = Object.keys(knownWhales)
const balances = await Promise.all(
whaleAddresses.slice(0, 10).map(async (address) => {
try {
const balance = await client.readContract({
address: tokenAddress as `0x${string}`,
abi: erc20Abi,
functionName: 'balanceOf',
args: [address as `0x${string}`],
})
return { address, balance }
} catch {
return { address, balance: 0n }
}
})
)
// Filter and format whales
const whales = balances
.filter((w) => w.balance > 0n)
.map((w) => {
const balanceFormatted = Number(formatUnits(w.balance, decimals))
const percentageSupply = (Number(w.balance) / Number(totalSupply)) * 100
const whaleInfo = knownWhales[w.address]
return {
address: w.address,
label: whaleInfo?.label || "Unknown Whale",
type: whaleInfo?.type || "whale",
balance: balanceFormatted,
percentageSupply: Number(percentageSupply.toFixed(4)),
lastActivity: new Date().toISOString().split('T')[0], // Real impl would query last tx
}
})
.filter((w) => w.balance * 1 >= minBalance) // Simplified USD conversion
.sort((a, b) => b.balance - a.balance)
return {
content: [
{
type: "text",
text: JSON.stringify(
{
tokenAddress,
chain,
totalWhales: whales.length,
whales,
},
null,
2
),
},
],
}
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify({ error: "Failed to detect whales", message: String(error) }, null, 2),
},
],
isError: true,
}
}
}
)
// Track whale movements
server.tool(
"wallet_whale_movements",
"Track recent large transactions (whale movements)",
{
chain: z
.enum(["ethereum", "bsc", "polygon", "arbitrum"])
.default("ethereum")
.describe("Blockchain network"),
minValueUSD: z.number().default(1000000).describe("Minimum transaction value in USD"),
hours: z.number().default(24).describe("Hours to look back"),
},
async ({ chain, minValueUSD, hours }) => {
// Mock movements - in production, query blockchain events
const movements = [
{
txHash: "0x1234...5678",
timestamp: new Date(Date.now() - 3600000).toISOString(),
from: "0x28C6c06298d514Db089934071355E5743bf21d60",
fromLabel: "Binance",
to: "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503",
toLabel: "Unknown Whale",
token: "ETH",
amount: 5000,
valueUSD: 17500000,
type: "exchange_withdrawal",
},
{
txHash: "0xabcd...efgh",
timestamp: new Date(Date.now() - 7200000).toISOString(),
from: "0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503",
fromLabel: "Unknown Whale",
to: "0xDA9dfA130Df4dE4673b89022EE50ff26f6EA73Cf",
toLabel: "Kraken",
token: "ETH",
amount: 2000,
valueUSD: 7000000,
type: "exchange_deposit",
},
].filter((m) => m.valueUSD >= minValueUSD)
const summary = {
exchangeInflows: movements
.filter((m) => m.type === "exchange_deposit")
.reduce((sum, m) => sum + m.valueUSD, 0),
exchangeOutflows: movements
.filter((m) => m.type === "exchange_withdrawal")
.reduce((sum, m) => sum + m.valueUSD, 0),
netFlow: 0,
}
summary.netFlow = summary.exchangeOutflows - summary.exchangeInflows
return {
content: [
{
type: "text",
text: JSON.stringify(
{
chain,
timeframe: `${hours}h`,
minValueFilter: minValueUSD,
movementCount: movements.length,
summary,
interpretation:
summary.netFlow > 0
? "Net outflow from exchanges - potentially bullish"
: "Net inflow to exchanges - potentially bearish",
movements,
},
null,
2
),
},
],
}
}
)
// Wallet profiling
server.tool(
"wallet_profile",
"Get comprehensive wallet profile and behavior analysis",
{
address: z.string().describe("Wallet address"),
chain: z
.enum(["ethereum", "bsc", "polygon", "arbitrum", "solana"])
.default("ethereum")
.describe("Blockchain network"),
},
async ({ address, chain }) => {
// Check if known wallet
const known = knownWhales[address]
const profile = {
address,
chain,
knownAs: known?.label || null,
type: known?.type || "unknown",
classification: {
isWhale: Math.random() > 0.7,
isSmartMoney: Math.random() > 0.8,
isBot: Math.random() > 0.9,
isExchange: !!known?.type?.includes("exchange"),
isProtocol: !!known?.type?.includes("protocol"),
},
holdings: {
estimatedValueUSD: Math.random() * 10000000,
topTokens: [
{ symbol: "ETH", percentage: 45 },
{ symbol: "USDC", percentage: 25 },
{ symbol: "WBTC", percentage: 15 },
],
},
activity: {
firstSeen: "2020-08-15",
lastActive: new Date().toISOString().split("T")[0],
totalTransactions: Math.floor(Math.random() * 1000) + 100,
avgTxPerWeek: Math.floor(Math.random() * 20) + 5,
},
tradingPatterns: {
preferredDex: "Uniswap",
avgTradeSize: Math.random() * 50000,
winRate: Math.random() * 0.3 + 0.5, // 50-80%
mostTradedPairs: ["ETH/USDC", "WBTC/ETH", "UNI/ETH"],
},
defiActivity: {
protocols: ["Uniswap", "Aave", "Compound", "Curve", "Lido"],
hasOpenPositions: true,
totalValueLocked: Math.random() * 1000000,
},
riskIndicators: {
interactedWithFlagged: false,
recentRugpullTokens: [],
suspiciousActivity: false,
},
}
return {
content: [
{
type: "text",
text: JSON.stringify(profile, null, 2),
},
],
}
}
)
// Similar wallets
server.tool(
"wallet_find_similar",
"Find wallets with similar behavior patterns",
{
address: z.string().describe("Reference wallet address"),
chain: z
.enum(["ethereum", "bsc", "polygon"])
.default("ethereum")
.describe("Blockchain network"),
limit: z.number().default(10).describe("Maximum results"),
},
async ({ address, chain, limit }) => {
// Mock similar wallets
const similarWallets = Array.from({ length: limit }, (_, i) => ({
address: `0x${Math.random().toString(16).slice(2, 42)}`,
similarityScore: Math.random() * 0.3 + 0.7, // 70-100%
commonTraits: [
Math.random() > 0.5 && "Same DEX usage",
Math.random() > 0.5 && "Similar holding patterns",
Math.random() > 0.5 && "Same DeFi protocols",
Math.random() > 0.5 && "Similar trading frequency",
].filter(Boolean),
estimatedValue: Math.random() * 5000000,
}))
return {
content: [
{
type: "text",
text: JSON.stringify(
{
referenceWallet: address,
chain,
similarWalletsFound: similarWallets.length,
wallets: similarWallets.sort((a, b) => b.similarityScore - a.similarityScore),
},
null,
2
),
},
],
}
}
)
// Wallet labels lookup
server.tool(
"wallet_lookup_label",
"Look up known labels for wallet addresses",
{
addresses: z.array(z.string()).describe("Array of wallet addresses to look up"),
},
async ({ addresses }) => {
const results = addresses.map((address) => {
const known = knownWhales[address]
return {
address,
label: known?.label || null,
type: known?.type || "unknown",
isKnown: !!known,
}
})
return {
content: [
{
type: "text",
text: JSON.stringify(
{
looked: addresses.length,
identified: results.filter((r) => r.isKnown).length,
results,
},
null,
2
),
},
],
}
}
)
// Token holder analysis
server.tool(
"wallet_token_holder_analysis",
"Analyze holder distribution for a token",
{
tokenAddress: z.string().describe("Token contract address"),
chain: z
.enum(["ethereum", "bsc", "polygon"])
.default("ethereum")
.describe("Blockchain network"),
},
async ({ tokenAddress, chain }) => {
// Mock analysis
const analysis = {
tokenAddress,
chain,
totalHolders: 45678,
distribution: {
top10Percentage: 45.2,
top50Percentage: 72.3,
top100Percentage: 81.5,
},
holderCategories: {
exchanges: { count: 15, percentage: 28.5 },
whales: { count: 45, percentage: 22.1 },
institutions: { count: 8, percentage: 12.3 },
retail: { count: 45610, percentage: 37.1 },
},
concentration: {
giniCoefficient: 0.82,
herfindahlIndex: 0.045,
interpretation: "High concentration - top holders control significant supply",
},
recentChanges: {
last7Days: {
newHolders: 234,
exitedHolders: 156,
netChange: 78,
},
},
}
return {
content: [
{
type: "text",
text: JSON.stringify(analysis, null, 2),
},
],
}
}
)
}