I spent way too long following “alpha” accounts on Twitter. They’d post screenshots of 100x trades, and I’d ape into whatever they mentioned. The result? I lost money consistently.
The problem wasn’t the tokens. It was that I had no idea whether these people were actually profitable. A screenshot of one winning trade means nothing if they lost money on the other 50.
So I built something better. A wallet analyzer that fetches every swap a wallet has ever made, calculates real P&L, and gives me a scorecard I can actually trust. No more guessing. No more trusting screenshots.
1. What We’re Building
The idea is simple. Take any Solana wallet address, pull their full trading history, and answer one question: does this wallet actually make money?
Here’s what the analyzer does:
- Fetches all swap transactions for a wallet
- Parses each swap to figure out what was bought and sold
- Tracks positions per token — average buy price, total cost, total received
- Calculates realized P&L (closed trades) and unrealized P&L (open positions)
- Generates a scorecard — win rate, average return, biggest wins and losses
2. Fetch Transaction History
First, we need every transaction signature for a wallet. Solana’s getSignaturesForAddress returns them in batches, so we paginate through the full history.
import { Connection, PublicKey } from "@solana/web3.js";
const connection = new Connection("https://api.mainnet-beta.solana.com", {
commitment: "confirmed",
});
async function getAllSignatures(wallet: string) {
const pubkey = new PublicKey(wallet);
const allSignatures: string[] = [];
let before: string | undefined = undefined;
while (true) {
const batch = await connection.getSignaturesForAddress(pubkey, {
before,
limit: 1000,
});
if (batch.length === 0) break;
for (const sig of batch) {
if (!sig.err) {
allSignatures.push(sig.signature);
}
}
before = batch[batch.length - 1].signature;
// Rate limiting — RPC nodes will reject you otherwise
await new Promise((r) => setTimeout(r, 200));
}
console.log(`Found ${allSignatures.length} successful transactions`);
return allSignatures;
}
I used to skip the rate limiting. Don’t. Public RPCs will block you fast, and even paid ones have limits.
3. Parse Swap Transactions
This is the hardest part. Not every transaction is a swap, and different DEXes structure their instructions differently. I focus on Raydium and Jupiter since they handle the majority of volume.
The trick is looking at the token balance changes in the transaction metadata. If SOL went out and a token came in, that’s a buy. Reverse is a sell.
interface SwapInfo {
signature: string;
timestamp: number;
tokenMint: string;
side: "buy" | "sell";
solAmount: number;
tokenAmount: number;
}
const WSOL_MINT = "So11111111111111111111111111111111111111112";
async function parseSwap(signature: string): Promise<SwapInfo | null> {
const tx = await connection.getParsedTransaction(signature, {
maxSupportedTransactionVersion: 0,
commitment: "confirmed",
});
if (!tx || !tx.meta || !tx.meta.postTokenBalances) return null;
const preBalances = tx.meta.preTokenBalances || [];
const postBalances = tx.meta.postTokenBalances;
// Find SOL balance change
const solChange =
(tx.meta.postBalances[0] - tx.meta.preBalances[0]) / 1e9;
// Find token balance changes (ignore WSOL)
let tokenMint: string | null = null;
let tokenChange = 0;
for (let i = 0; i < postBalances.length; i++) {
const post = postBalances[i];
if (post.mint === WSOL_MINT) continue;
if (post.owner !== tx.transaction.message.accountKeys[0].pubkey.toString())
continue;
const preEntry = preBalances.find(
(p) => p.accountIndex === post.accountIndex
);
const preBal = preEntry?.uiTokenAmount?.uiAmount || 0;
const postBal = post.uiTokenAmount?.uiAmount || 0;
if (postBal !== preBal) {
tokenMint = post.mint;
tokenChange = postBal - preBal;
}
}
if (!tokenMint || tokenChange === 0) return null;
return {
signature,
timestamp: tx.blockTime || 0,
tokenMint,
side: tokenChange > 0 ? "buy" : "sell",
solAmount: Math.abs(solChange),
tokenAmount: Math.abs(tokenChange),
};
}
It’s not perfect. Some complex transactions with multiple swaps in one tx will get missed. But for 90% of trades, this works.
4. Build a Position Tracker
Now we aggregate every swap into positions. For each token, we track total SOL spent buying, total SOL received selling, and remaining token balance.
interface Position {
tokenMint: string;
totalBought: number; // total SOL spent
totalSold: number; // total SOL received
tokenBalance: number;
avgBuyPrice: number; // SOL per token
trades: number;
wins: number;
}
function buildPositions(swaps: SwapInfo[]): Map<string, Position> {
const positions = new Map<string, Position>();
// Sort chronologically
const sorted = [...swaps].sort((a, b) => a.timestamp - b.timestamp);
for (const swap of sorted) {
let pos = positions.get(swap.tokenMint);
if (!pos) {
pos = {
tokenMint: swap.tokenMint,
totalBought: 0,
totalSold: 0,
tokenBalance: 0,
avgBuyPrice: 0,
trades: 0,
wins: 0,
};
positions.set(swap.tokenMint, pos);
}
pos.trades++;
if (swap.side === "buy") {
const prevCost = pos.avgBuyPrice * pos.tokenBalance;
pos.tokenBalance += swap.tokenAmount;
pos.totalBought += swap.solAmount;
pos.avgBuyPrice =
(prevCost + swap.solAmount) / pos.tokenBalance;
} else {
const sellPrice = swap.solAmount / swap.tokenAmount;
if (sellPrice > pos.avgBuyPrice) {
pos.wins++;
}
pos.tokenBalance -= swap.tokenAmount;
pos.totalSold += swap.solAmount;
}
}
return positions;
}
The average buy price calculation matters a lot here. If someone buys a token three times at different prices, we need a weighted average to know whether their sells are profitable. I’ve seen other tools that just use the first buy price — that’s wrong and will skew the results.
5. Calculate P&L
With positions built, P&L is straightforward. Realized P&L is what they sold minus what they bought (for closed portions). Unrealized P&L needs current token prices.
interface WalletPnL {
realizedPnL: number;
unrealizedPnL: number;
totalPnL: number;
winRate: number;
totalTrades: number;
avgReturn: number;
biggestWin: { token: string; pnl: number };
biggestLoss: { token: string; pnl: number };
}
async function calculatePnL(
positions: Map<string, Position>
): Promise<WalletPnL> {
let realizedPnL = 0;
let unrealizedPnL = 0;
let totalWins = 0;
let totalTrades = 0;
let biggestWin = { token: "", pnl: -Infinity };
let biggestLoss = { token: "", pnl: Infinity };
for (const [mint, pos] of positions) {
const realized = pos.totalSold - pos.totalBought;
realizedPnL += realized;
totalTrades += pos.trades;
totalWins += pos.wins;
if (realized > biggestWin.pnl) {
biggestWin = { token: mint, pnl: realized };
}
if (realized < biggestLoss.pnl) {
biggestLoss = { token: mint, pnl: realized };
}
// For unrealized, fetch current price if they still hold tokens
if (pos.tokenBalance > 0) {
const currentPrice = await getCurrentPrice(mint);
const currentValue = pos.tokenBalance * currentPrice;
const costBasis = pos.avgBuyPrice * pos.tokenBalance;
unrealizedPnL += currentValue - costBasis;
}
}
const winRate = totalTrades > 0 ? (totalWins / totalTrades) * 100 : 0;
return {
realizedPnL,
unrealizedPnL,
totalPnL: realizedPnL + unrealizedPnL,
winRate,
totalTrades,
avgReturn: totalTrades > 0 ? realizedPnL / totalTrades : 0,
biggestWin,
biggestLoss,
};
}
For getCurrentPrice, I use Jupiter’s price API. It’s free and covers most Solana tokens:
async function getCurrentPrice(mint: string): Promise<number> {
const resp = await fetch(
`https://price.jup.ag/v6/price?ids=${mint}&vsToken=So11111111111111111111111111111111111111112`
);
const data = await resp.json();
return data.data?.[mint]?.price || 0;
}
6. Generate the Scorecard
Here’s where it gets useful. Instead of just raw numbers, we build a scorecard that tells you at a glance whether a wallet is worth following.
function generateScorecard(wallet: string, pnl: WalletPnL) {
console.log(`\n=== Wallet Scorecard: ${wallet.slice(0, 8)}... ===`);
console.log(`Total Trades: ${pnl.totalTrades}`);
console.log(`Win Rate: ${pnl.winRate.toFixed(1)}%`);
console.log(`Realized P&L: ${pnl.realizedPnL.toFixed(4)} SOL`);
console.log(`Unrealized P&L: ${pnl.unrealizedPnL.toFixed(4)} SOL`);
console.log(`Total P&L: ${pnl.totalPnL.toFixed(4)} SOL`);
console.log(`Avg Return/Trade: ${pnl.avgReturn.toFixed(4)} SOL`);
console.log(`Biggest Win: ${pnl.biggestWin.pnl.toFixed(4)} SOL`);
console.log(`Biggest Loss: ${pnl.biggestLoss.pnl.toFixed(4)} SOL`);
// Simple grade
const grade =
pnl.winRate > 60 && pnl.totalPnL > 0
? "A"
: pnl.winRate > 50 && pnl.totalPnL > 0
? "B"
: pnl.totalPnL > 0
? "C"
: "F";
console.log(`Grade: ${grade}`);
}
A wallet with a 65% win rate and positive P&L over hundreds of trades? That’s someone worth watching. A wallet with two lucky 50x trades and 40 losses? Skip.
7. Finding Wallets to Analyze
This is the part most people get wrong. They analyze wallets that are already famous. By the time a wallet is well-known, the edge is gone.
Here’s how I find wallets worth looking at:
- Birdeye and GMGN leaderboards — Sort by P&L over 30 days. Ignore anyone in the top 5 — they’re likely gaming the system. Focus on ranks 20-100.
- Early buyers on tokens that pumped — When a token does a 20x, go look at who bought in the first hour. Those wallets found it early for a reason.
- DEX Screener’s top traders — For any specific token, you can see who the top traders are. Cross-reference wallets that appear as top traders on multiple different tokens.
I run my analyzer against a batch of candidate wallets and rank them:
async function rankWallets(wallets: string[]) {
const results: { wallet: string; pnl: WalletPnL }[] = [];
for (const wallet of wallets) {
console.log(`Analyzing ${wallet.slice(0, 8)}...`);
const sigs = await getAllSignatures(wallet);
const swaps: SwapInfo[] = [];
for (const sig of sigs) {
const swap = await parseSwap(sig);
if (swap) swaps.push(swap);
await new Promise((r) => setTimeout(r, 100));
}
const positions = buildPositions(swaps);
const pnl = await calculatePnL(positions);
results.push({ wallet, pnl });
}
// Sort by total P&L descending
results.sort((a, b) => b.pnl.totalPnL - a.pnl.totalPnL);
console.log("\n=== Wallet Rankings ===");
results.forEach((r, i) => {
console.log(
`${i + 1}. ${r.wallet.slice(0, 8)}... | ` +
`Win: ${r.pnl.winRate.toFixed(1)}% | ` +
`P&L: ${r.pnl.totalPnL.toFixed(2)} SOL | ` +
`Trades: ${r.pnl.totalTrades}`
);
});
}
I keep a “follow list” of about 15 wallets that consistently show up as profitable across different time periods. I re-run the analysis monthly and drop anyone whose numbers slip.
8. The Limitations You Need to Know
Before you trust any wallet’s numbers blindly, understand what this tool can’t tell you.
Wash trading is everywhere. A wallet can buy and sell between its own wallets to fake volume and P&L. If a wallet’s numbers look impossibly good, they probably are.
People use multiple wallets. A trader might route winning trades through one wallet and losing trades through another. The wallet you’re analyzing might only show half the picture.
Insider info doesn’t transfer. Some wallets are profitable because they have access to information you don’t — dev wallets, KOL presale allocations, or coordinated pump groups. Copying their buys without knowing their exit plan is a losing strategy.
Timing matters more than direction. Even if you spot a smart wallet buying something, by the time you see it and execute, the price might have already moved 30%. On Solana, bots are watching the same wallets you are.
9. What Actually Works
I’ve been running this system for months now, and here’s what I’ve learned.
The real value isn’t in copying trades. It’s in pattern recognition. When three wallets on my follow list all buy the same token within an hour, that’s a signal. When a consistently profitable wallet starts accumulating a token I’ve never heard of, I go research it — I don’t blindly buy it.
I also use the analyzer defensively. Before I buy any token, I check who the top holders are and run them through the scorecard. If the top wallets are all low-grade or show signs of wash trading, I stay away.
The alpha isn’t in knowing what smart money bought. It’s in understanding why they bought it and getting there before everyone else figures it out. Build the tool, study the patterns, and develop your own thesis. That’s what separates the people who make money from the people who just follow screenshots.