How to Build a Solana Wallet Scoring System: Combine On-Chain Metrics Into One Score

Tools Mentioned in This Article
Compare features, read reviews, and check live health scores on MadeOnSol.

Compare features, read reviews, and check live health scores on MadeOnSol.
Solana airdrop tracker and DeFi farming hub with smart notifications

On-chain casino with jackpots, coinflip, and PvP games on Solana

Curated directory of Solana tools with reviews, comparisons, and guides

Register human-readable .sol domain names as your Web3 identity on Solana

Decentralized on-chain identity and reputation protocol for Solana wallets

Learn how to build a Solana token analytics dashboard that combines real-time price feeds, holder data, trading volume, and liquidity metrics into a single view using TypeScript and the MadeOnSol API.

Solana validators prune old data aggressively, making historical analysis difficult. Learn how to access Solana historical data using BigQuery, Flipside, Dune, Helius DAS API, and archival services — with practical query examples.

Learn how to set up Solana webhooks for real-time blockchain alerts. Compare Helius, QuickNode, Shyft, and Triton webhook providers, with TypeScript code examples for wallet monitoring, NFT sales alerts, and payment confirmation.
Get this data via API
Stream real-time KOL trades, PnL rankings, and coordination signals programmatically.
import { MadeOnSol } from "madeonsol";
const client = new MadeOnSol({ apiKey: "msk_your_key" });
// Real-time KOL trades
const { trades } = await client.kol.feed({ limit: 10, action: "buy" });
// KOL convergence signals
const { tokens } = await client.kol.coordination({ min_kols: 3 });Get weekly Solana ecosystem insights delivered to your inbox.
Not all wallets are created equal. A wallet that has traded profitably for six months across hundreds of tokens is fundamentally different from one that got lucky on a single memecoin. But most trading tools treat them the same — a buy signal is a buy signal, regardless of who is behind it.
A wallet scoring system fixes this. By combining multiple on-chain metrics into a single composite score, you can rank any Solana wallet on a spectrum from low-quality noise to high-conviction alpha. This guide walks through the architecture, metrics, data sources, and code needed to build one from scratch.
Raw wallet tracking gives you a firehose of data. Thousands of wallets are making trades every minute on Solana. Without scoring, you cannot separate the signal from the noise.
A well-designed scoring system lets you:
If you are already tracking smart money on Solana, scoring is the natural next step to make that data actionable.
A robust Solana wallet scoring system combines at least six on-chain dimensions. Each captures a different aspect of wallet quality.
Older wallets with long transaction histories are more trustworthy than fresh wallets. A wallet that has survived multiple market cycles has a track record you can evaluate. New wallets might be sybils, bots, or one-trade wonders.
Data source: Use Helius getSignaturesForAddress to pull the full transaction history. The timestamp of the earliest transaction gives you wallet age.
Profit and loss is the most obvious metric, but it needs careful calculation. You must track buy cost basis per token, match sells against buys (FIFO or average cost), and account for partial exits.
Data source: Cielo Finance provides pre-calculated PnL for Solana wallets. Alternatively, reconstruct it from raw swap transactions via Helius parsed transaction history.
Win rate measures what percentage of a wallet's token trades ended in profit. A wallet with 45% win rate across 200 trades is far more reliable than one with 80% win rate across 5 trades. Always consider win rate alongside sample size.
Data source: Derive from the same PnL data. A "win" is any token position that was closed with positive realized PnL.
Wallets that profit across many different tokens demonstrate repeatable skill rather than luck. A wallet that made money on 50 different tokens is more impressive than one that made the same amount on a single 100x play.
Data source: Count unique token mints traded from the wallet's swap history. Weight diversity by the number of profitable unique tokens versus total unique tokens traded.
Active wallets generate more data points, which makes their other metrics more statistically significant. A wallet averaging 10+ trades per week over months gives you high confidence. A wallet with 3 trades total does not.
Data source: Count transactions over rolling time windows (7d, 30d, 90d) from Helius transaction signatures.
How long a wallet holds positions reveals its strategy. Diamond-hands wallets that hold through drawdowns behave differently from quick-flip scalpers. Neither is inherently better, but understanding the pattern helps you match wallets to your own strategy.
Data source: Calculate average hold time per token by measuring the time between first buy and last sell for each position.
The foundation of any wallet scoring system is reliable on-chain data. Here is how to fetch a wallet's complete trading history using the Helius API.
const HELIUS_API_KEY = process.env.HELIUS_API_KEY;
const BASE_URL = `https://api.helius.xyz/v0`;
interface WalletTransaction {
signature: string;
timestamp: number;
type: string;
tokenTransfers: TokenTransfer[];
}
interface TokenTransfer {
mint: string;
fromUserAccount: string;
toUserAccount: string;
tokenAmount: number;
}
async function getWalletHistory(
walletAddress: string,
limit = 1000
): Promise<WalletTransaction[]> {
const transactions: WalletTransaction[] = [];
let beforeSignature: string | undefined;
while (transactions.length < limit) {
const url = new URL(
`${BASE_URL}/addresses/${walletAddress}/transactions`
);
url.searchParams.set("api-key", HELIUS_API_KEY);
url.searchParams.set("limit", "100");
if (beforeSignature) {
url.searchParams.set("before", beforeSignature);
}
const response = await fetch(url.toString());
const batch = await response.json();
if (!batch.length) break;
transactions.push(...batch);
beforeSignature = batch[batch.length - 1].signature;
}
return transactions;
}
This gives you the raw transaction data. From here, filter for swap transactions (type SWAP) and extract the token transfers to build a per-token trade ledger.
Once you have the raw transactions, group them by token mint and calculate realized PnL per position.
interface TokenPosition {
mint: string;
buys: { amount: number; costSol: number; timestamp: number }[];
sells: { amount: number; receivedSol: number; timestamp: number }[];
}
function calculateWinRate(positions: TokenPosition[]): {
winRate: number;
totalTrades: number;
wins: number;
} {
let wins = 0;
let closed = 0;
for (const position of positions) {
const totalCost = position.buys.reduce((s, b) => s + b.costSol, 0);
const totalReceived = position.sells.reduce(
(s, s2) => s + s2.receivedSol, 0
);
// Only count positions that have been at least partially sold
if (position.sells.length === 0) continue;
closed++;
if (totalReceived > totalCost) {
wins++;
}
}
return {
winRate: closed > 0 ? wins / closed : 0,
totalTrades: closed,
wins,
};
}
A common mistake is counting open positions (tokens bought but never sold) as losses. Exclude them entirely or score them separately based on current unrealized PnL.
With individual metrics calculated, combine them into a single score using weighted normalization.
interface WalletMetrics {
walletAgeInDays: number;
realizedPnlSol: number;
winRate: number; // 0 to 1
uniqueTokensTraded: number;
weeklyTxFrequency: number;
avgHoldTimeHours: number;
totalClosedTrades: number;
}
interface ScoringWeights {
age: number;
pnl: number;
winRate: number;
diversity: number;
frequency: number;
holdPattern: number;
}
const DEFAULT_WEIGHTS: ScoringWeights = {
age: 0.10,
pnl: 0.25,
winRate: 0.25,
diversity: 0.15,
frequency: 0.10,
holdPattern: 0.15,
};
function normalize(value: number, min: number, max: number): number {
return Math.max(0, Math.min(1, (value - min) / (max - min)));
}
function computeWalletScore(
metrics: WalletMetrics,
weights: ScoringWeights = DEFAULT_WEIGHTS
): number {
// Require minimum sample size for statistical confidence
const sampleMultiplier = Math.min(1, metrics.totalClosedTrades / 30);
const scores = {
age: normalize(metrics.walletAgeInDays, 0, 365),
pnl: normalize(metrics.realizedPnlSol, -10, 500),
winRate: normalize(metrics.winRate, 0.15, 0.60),
diversity: normalize(metrics.uniqueTokensTraded, 1, 100),
frequency: normalize(metrics.weeklyTxFrequency, 0, 50),
holdPattern: normalize(metrics.avgHoldTimeHours, 0.5, 168),
};
const raw =
scores.age * weights.age +
scores.pnl * weights.pnl +
scores.winRate * weights.winRate +
scores.diversity * weights.diversity +
scores.frequency * weights.frequency +
scores.holdPattern * weights.holdPattern;
// Scale by sample confidence and convert to 0-100
return Math.round(raw * sampleMultiplier * 100);
}
The sampleMultiplier is critical. Without it, a wallet with 2 trades and a 100% win rate would score higher than a wallet with 200 trades and a 45% win rate. The multiplier penalizes wallets with insufficient data, requiring at least 30 closed trades for full scoring confidence.
The default weights above are a starting point. You should adjust them based on your use case:
pnl and winRate weights. You care about profitability above all.age and diversity weights. Sybils tend to be new wallets with narrow activity.frequency and diversity weights. Alpha wallets are actively exploring new tokens.Test your weights against known wallets. Pick 10 wallets you consider high-quality and 10 you consider low-quality. A good scoring system should cleanly separate the two groups.
The most immediate application of wallet scoring is improving copy-trading systems. Instead of copying every trade from a followed wallet, gate the copy-trade execution on the wallet's score.
Implementation approach:
The MadeOnSol KOL Tracker already surfaces wallet performance data that feeds directly into scoring. Combining KOL tracker data with your own scoring layer gives you a filtered view of only the highest-quality signals from top-performing KOL wallets.
Sybil wallets created for airdrop farming share common patterns that scoring exposes:
A wallet scoring system flags these automatically. Wallets scoring below 20 out of 100 that suddenly interact with a new protocol are almost certainly sybils. This is valuable both for protocol teams running airdrops and for traders who want to estimate real user counts.
The most powerful use of wallet scoring is discovering new alpha wallets before they appear on public leaderboards. The approach is to monitor whale wallets on Solana and score every wallet that interacts with trending tokens early.
Discovery pipeline:
Nansen provides pre-built smart money labels that can serve as ground truth for validating your scoring model against established alpha wallets.
Real-world wallet scoring requires handling several edge cases:
Multi-wallet users: Sophisticated traders split activity across multiple wallets. Your scoring system sees each wallet independently. This is actually fine — if a wallet scores well on its own, the signal is valid regardless of whether the owner has other wallets.
Bot wallets: MEV bots and arbitrage bots will score high on frequency and win rate but low on diversity and hold time. Decide whether to include or exclude them based on your use case. For copy-trading, bot wallets are usually not useful to follow.
Dormant wallets: A wallet that was highly active and profitable six months ago but has not traded since presents a challenge. Apply a time decay factor — reduce scores for wallets with no activity in the last 30 days.
function applyTimeDecay(score: number, lastActivityDays: number): number {
if (lastActivityDays <= 30) return score;
const decayFactor = Math.max(0.3, 1 - (lastActivityDays - 30) / 180);
return Math.round(score * decayFactor);
}
A complete wallet scoring pipeline runs as a scheduled job:
The entire system can run on a single server with the Helius free tier handling most data needs. As you scale to thousands of wallets, consider caching transaction data locally and only fetching incremental updates.
The Helius free tier provides 100,000 credits per day, and each getSignaturesForAddress call costs a small number of credits. In practice you can score 200 to 500 wallets daily on the free tier if you cache transaction history and only fetch new data incrementally. For larger-scale scoring, Helius paid plans or a combination of Helius and Birdeye data provides enough throughput for thousands of wallets.
A wallet needs at least 30 closed token positions over a period of at least 60 days for the score to be statistically meaningful. Below that threshold, apply the sample size multiplier discussed above to discount the score. Wallets with fewer than 10 closed trades should not be used for copy-trading decisions regardless of their apparent win rate, because the variance is too high to distinguish skill from luck.
Yes, in most cases. Wallet behavior changes over time, and a wallet that was profitable six months ago may have shifted strategy. A practical approach is to calculate metrics over two windows — the full history and the last 90 days — then weight the recent window at 60% and the full history at 40%. This captures both long-term track record and current form.
Run a backtest. Take a snapshot of wallet scores from 30 days ago, then measure the actual trading performance of those wallets over the following 30 days. Group wallets into score quartiles (0-25, 26-50, 51-75, 76-100) and compare average PnL across groups. A working scoring system should show a clear positive correlation between score quartile and subsequent profitability. If it does not, revisit your weights and normalization ranges.