Solana Wallet PnL & Smart-Money Profiler avatar

Solana Wallet PnL & Smart-Money Profiler

Pricing

from $5.00 / 1,000 wallet profileds

Go to Apify Store
Solana Wallet PnL & Smart-Money Profiler

Solana Wallet PnL & Smart-Money Profiler

Solana wallet PnL, win rate, holding times and a transparent smart-money score from raw on-chain data via your own RPC (Helius, Triton, QuickNode). No scraping. Errors are free.

Pricing

from $5.00 / 1,000 wallet profileds

Rating

0.0

(0)

Developer

Simon Liarsgård

Simon Liarsgård

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

17 hours ago

Last modified

Share

Realized and unrealized PnL, win rate, holding times, top wins/losses and a transparent smart-money score for any Solana wallet, computed straight from on-chain data through your own RPC.

Paste wallet addresses, plug in your Helius / Triton / QuickNode URL (or use the public endpoint for a quick try), and get one clean row per wallet. Every number can be traced back to on-chain transactions: the scoring formula is documented below, and each result carries its own score breakdown and diagnostics.

$0.005 per profiled wallet. Invalid addresses and RPC failures are free.

Who it is for

  • Copy-trading and alpha research: shortlist wallets that actually make money, with win rate and holding time next to PnL.
  • Bot and sniper detection: seconds-long median holds and extreme trade counts stand out immediately.
  • Dashboards and agents: call it from the Apify API, Make, n8n, Zapier or any MCP-capable AI agent and get structured JSON back.
  • Due diligence: check a KOL's or a fund's on-chain track record before you trust their calls.

How to use it

  1. Add one or more wallet addresses in Wallet addresses.
  2. Paste your RPC URL in Solana RPC URL (recommended), or leave it empty to try the public endpoint (capped at 50 transactions per wallet).
  3. Optionally change the time window, the transaction cap and the smart-money thresholds.
  4. Click Start. Results appear in the Overview and Detail dataset views; export as JSON, CSV or Excel.

What it does

For every wallet and time window (default: last 30 days) the actor:

  1. reads the wallet's transaction history over standard Solana JSON-RPC (getSignaturesForAddress + getTransaction);
  2. detects swaps on any DEX or aggregator: Jupiter, Pump.fun, PumpSwap, Raydium, Orca, Meteora and others, multi-hop routes included. Detection works from balance changes, not from program-specific decoders;
  3. runs an average-cost PnL ledger per token, in SOL (SOL + wSOL) and separately in USD (USDC + USDT);
  4. computes win rate, number of trades, average/median holding time, top wins and losses, most traded tokens, first/last trade;
  5. scores the wallet with a documented smart-money formula and sets a smartMoney flag based on thresholds you choose.

Why on-chain and bring-your-own RPC?

  • No scraping. The actor only talks to a Solana RPC node, so there is no website layout to break and no third-party terms to worry about.
  • Your RPC, your limits. Use the RPC plan you already pay for. Every provider exposing standard JSON-RPC works. The URL is stored as an encrypted secret input, and it's redacted in logs.
  • Transparent methodology. No black-box "smart score". You get each score component, its weight, its inputs and the formula, and every threshold check behind smartMoney.
  • Read-only. The actor never signs or sends transactions and never touches private keys.

Input

{
"wallets": ["FEdz1g8HfaAUSoJbSB1sXfcweh3qzye4XJAmaafUmA96", "HHEF5WjjKnM9xAxdMjEH2mNFBHSfUtxq79dkZXVCJ7fM"],
"rpcUrl": "https://mainnet.helius-rpc.com/?api-key=YOUR_KEY",
"lookbackDays": 30,
"maxTransactionsPerWallet": 500,
"maxRequestsPerSecond": 20,
"minWinRate": 0.55,
"minRealizedPnlSol": 1,
"minClosedPositions": 5,
"includeTokenBreakdown": true,
"includeTrades": false
}
FieldDefaultNotes
wallets(required)Base58 addresses. Duplicates are removed; invalid ones give a free error item.
rpcUrlpublic mainnetSecret field. Strongly recommended: your own Helius/Triton/QuickNode URL. On the public endpoint, history is capped at 50 tx per wallet.
lookbackDays / startDate / endDate30 daysstartDate/endDate (UTC, YYYY-MM-DD) override the lookback.
maxTransactionsPerWallet500 (max 5000)Newest first. If a wallet has more inside the window, the item gets truncated: true.
maxRequestsPerSecond, concurrency5, 3Upper bounds. The actor also slows itself down when the RPC rate limits.
useBatchRequeststrueJSON-RPC batches, with automatic fallback to single requests if the endpoint rejects them.
requestTimeoutSecs, maxRetries30, 6Per-request timeout; retries with exponential backoff and jitter, honoring Retry-After.
minWinRate, minRealizedPnlSol, minClosedPositions0.55, 1, 5Thresholds for the smartMoney flag.
includeTokenBreakdown, includeTradestrue, falsePer-token ledgers / every classified swap.
maxWallets100Safety limit per run.

Output

One dataset item per wallet. Here is a real item from a test run (2026-10-29, public RPC, 50 tx), trimmed to one token:

{
"wallet": "HHEF5WjjKnM9xAxdMjEH2mNFBHSfUtxq79dkZXVCJ7fM",
"status": "ok",
"truncated": true,
"truncationReason": "max_transactions_per_wallet",
"windowStart": "2026-09-29T01:47:51.000Z",
"windowEnd": "2026-10-29T01:47:51.000Z",
"realizedPnlSol": 2.41935883,
"unrealizedPnlSol": 0,
"totalPnlSol": 2.41935883,
"realizedPnlSolAfterFees": 2.416972827,
"swapFeesSol": 0.002386003,
"realizedPnlUsd": 0,
"unrealizedPnlUsd": 0,
"volumeSol": 27.721901982,
"winRate": 0.6667,
"closedPositions": 6,
"winningPositions": 4,
"trades": 13,
"buys": 6,
"sells": 7,
"tokensTraded": 6,
"tokensWithUnknownCostBasis": 0,
"avgHoldingTimeSec": 1,
"medianHoldingTimeSec": 1,
"firstTradeAt": "2026-10-29T00:20:22.000Z",
"lastTradeAt": "2026-10-29T01:01:28.000Z",
"smartMoneyScore": 49.8,
"scoreComponents": {
"winRate": { "value": 0.625, "weight": 0.35, "points": 21.88, "formula": "(winningPositions + 1) / (closedPositions + 2)", "inputs": { "winningPositions": 4, "closedPositions": 6 } },
"realizedPnl": { "value": 0.1947, "weight": 0.3, "points": 5.84, "inputs": { "realizedPnlSolAfterFees": 2.416973 } },
"activity": { "value": 0.6, "weight": 0.15, "points": 9, "inputs": { "closedPositions": 6, "tradesPerDay": 13 } },
"consistency": { "value": 0.6558, "weight": 0.2, "points": 13.12, "inputs": { "winningSolPositions": 4, "largestWinSol": 1.062164, "grossWinsSol": 3.085804 } }
},
"smartMoney": true,
"smartMoneyCriteria": {
"winRate": { "required": 0.55, "actual": 0.6667, "pass": true },
"realizedPnlSolAfterFees": { "required": 1, "actual": 2.416973, "pass": true },
"closedPositions": { "required": 5, "actual": 6, "pass": true }
},
"topWins": { "sol": [{ "mint": "CX437jnJpHhQEscuf9fB1bbX73mRh7afoEcm84Uypump", "realizedPnl": 1.062163617 }], "usd": [] },
"topLosses": { "sol": [{ "mint": "BLoBoDntf68U172TpR5z1jRWCSww3eTHE9JU7VeUpump", "realizedPnl": -0.638908892 }], "usd": [] },
"mostTradedTokens": [{ "mint": "CX437jnJpHhQEscuf9fB1bbX73mRh7afoEcm84Uypump", "trades": 3, "buys": 1, "sells": 2, "volumeSol": 5.076854977, "volumeUsd": 0 }],
"unrealizedPriceSource": "last_observed_wallet_swap",
"pnlMethod": "average_cost",
"tokens": [
{
"mint": "BLoBoDntf68U172TpR5z1jRWCSww3eTHE9JU7VeUpump",
"trades": 2, "buys": 1, "sells": 1, "remainingQty": 0, "unknownCostBasisQty": 0,
"sol": { "spent": 2.038381386, "received": 1.399472494, "matchedQty": 46958769.19543, "realizedPnl": -0.638908892, "remainingQty": 0, "lastPrice": 2.980215449e-8, "unrealizedPnl": 0 },
"usd": null
}
],
"diagnostics": {
"signaturesScanned": 51, "transactionsFetched": 50, "failedTxSkipped": 0, "nullTransactions": 0,
"swapsClassified": 13, "tokenToTokenSwaps": 0, "quoteToQuoteSwaps": 0,
"unclassified": { "no_wallet_balance_change": 35, "transfer_or_single_sided": 2 },
"rpcCalls": 76, "rpcRetries": 0, "batchMode": "supported", "durationMs": 116950
}
}

status values: ok, no_swaps (activity but no classified swap), no_activity (no signatures in the window), invalid_input, error (with errorCode and errorMessage).

The dataset has two views: Overview (wallet, status, realized/unrealized SOL PnL, win rate, trades, score, flag, first/last trade) and Detail (everything above). A RUN_SUMMARY record in the key-value store lists counts, wallets skipped because of the charge limit, and RPC statistics.

Pricing

Pay per event: $0.005 per profiled wallet (wallet-profiled), plus Apify's standard actor-start event of $0.00005 (one per GB of memory; the default 256 MB run counts as one).

  • Charged once per wallet with status ok, no_swaps or no_activity. The RPC work was done and the answer is valid.
  • Not charged: invalid addresses, wallets that failed because of RPC errors, duplicates.
  • The actor respects your max total charge per run: it stops before profiling a wallet it can't bill, and lists the skipped wallets in RUN_SUMMARY. The lowest max charge you can set is $0.50, which covers 100 wallets (the default maxWallets).
  • Your RPC usage is billed by your RPC provider. Roughly: signatures pages + 1 getTransaction per transaction.
Wallets profiledCost
10$0.05
1,000$5.00
10,000$50.00

FAQ

Which RPC should I use? Any standard Solana JSON-RPC endpoint: Helius, Triton, QuickNode, Alchemy, your own node. Free tiers work, just set maxRequestsPerSecond to what your plan allows. The public api.mainnet-beta.solana.com allows about 10 getTransaction calls per 10 seconds, so it's only good for a quick look (capped at 50 tx per wallet).

Is my RPC key safe? rpcUrl is a secret input, stored encrypted by Apify, and the actor only ever logs the host (https://host/***).

Why is unrealized PnL sometimes 0 on an open position? There's no external price API. Open positions are marked at the last price this wallet itself paid or received for the token in the analyzed data. If the last trade was the buy, unrealized PnL is 0 by construction. See Limitations.

Why SOL and USD separately? Converting between SOL and USD needs a historical SOL price, which pure on-chain data can't provide reliably. So SOL-quoted trades (SOL/wSOL) and stable-quoted trades (USDC/USDT, counted 1:1 as USD) run in separate ledgers, and both are reported.

Does it detect bots? Only lightly: the activity component halves above 200 trades/day. Bundlers and snipers can still score well, so look at avgHoldingTimeSec / medianHoldingTimeSec (seconds-long holds are a strong bot signal).

Can I run it on a schedule or from code? Yes. Use Apify Schedules for daily snapshots, or call the actor through the Apify API / client libraries and read the dataset. Webhooks can notify your system when a run finishes.

Can an AI agent use it? Yes. Every public Apify actor is reachable through Apify's MCP server (https://mcp.apify.com), so Claude, ChatGPT and other MCP clients can profile wallets directly.

Does it trade or need my private key? No. It is strictly read-only and only needs public wallet addresses.

Does it work with Pump.fun? Yes. Bonding-curve and PumpSwap trades are plain balance changes, so they're picked up like any other swap; no special casing.

How PnL is calculated (average cost)

Each mint has two cost pools: SOL-quoted and USD-quoted (USDC/USDT 1:1).

  • Buy: adds the quantity and its cost to the pool of its quote ledger, and updates the pool's quantity-weighted average acquisition time.
  • Sell: draws from the pool of its own ledger at average cost. Realized PnL = proceeds for that quantity − its average cost.
    • If that pool runs out, the rest is drawn from the other ledger's pool. The quantity leaves the position, but no PnL is computed because the units differ (crossLedgerQty).
    • Anything still left has an unknown cost basis. It's reported as unknownCostBasisQty, and its proceeds as unmatchedProceeds; it never inflates PnL.
  • Unrealized: remaining quantity × the last swap price seen for that mint in that ledger, minus the remaining cost.
  • Order: transactions are processed oldest first. Same-slot trades keep their on-chain order (signatures arrive newest first and are reversed), so a buy and a sell in the same slot match correctly.
  • Dust: quantities are floats derived from integer on-chain amounts. Leftovers below half of the token's smallest unit (0.5 × 10^-decimals) are treated as zero, so fully sold positions don't linger.
  • Fees: swapFeesSol sums network fees on classified swaps. stableSwapSolOverheadSol sums SOL the wallet spent next to stable-quoted swaps (tips); SOL a relayer puts into the wallet is ignored. realizedPnlSolAfterFees = realizedPnlSol − swapFeesSol − stableSwapSolOverheadSol.
  • A trade is one classified swap.
  • Win rate = closed positions with realized PnL > 0 / closed positions. A closed position is a (mint, ledger) pair with at least one sell matched against a known cost basis, whether fully or partially sold.
  • Holding time: one sample per sell = sell time − the pool's quantity-weighted average acquisition time, which is the average-cost analogue of lot age. avgHoldingTimeSec and medianHoldingTimeSec summarize these samples.

Smart-money score (0–100)

score = 100 × (0.35·W + 0.30·P + 0.15·A + 0.20·C). Every component is in [0, 1] and is reported with its inputs:

ComponentFormulaWhy
W, win rate(wins + 1) / (closed + 2)Laplace-smoothed, so 1/1 doesn't count as 100%.
P, realized PnLmax(0, pnl) / (max(0, pnl) + 10), with pnl = realizedPnlSolAfterFeesSaturating: 10 SOL gives 0.5, 90 SOL gives 0.9. Losses give 0.
A, activity sanitymin(closed / 10, 1) × (tradesPerDay > 200 ? 0.5 : 1)Needs a sample size; damps obvious high-frequency bots.
C, consistency1 − largestWinSol / grossWinsSol if ≥ 2 winning SOL positions, else 0Penalizes one lucky trade.

smartMoney is true only if win rate ≥ minWinRate, realizedPnlSolAfterFees ≥ minRealizedPnlSol and closed positions ≥ minClosedPositions. Each check is reported in smartMoneyCriteria.

Limitations (please read)

  • Unrealized PnL is not a market price. It uses the last swap price observed in the wallet's own trades within the fetched data (unrealizedPriceSource: "last_observed_wallet_swap"). A token that has since gone to zero, or 10×'d, isn't reflected.
  • SOL-denominated. USD-quoted PnL is reported separately and never converted. If a token is bought with USDC and sold for SOL, the quantity is closed out but no PnL is computed for that part (crossLedgerQty).
  • Transfers break cost basis. Tokens that arrive by transfer, airdrop or before the window have no known cost. Sells of them only realize PnL on the quantity bought inside the window; the rest is unknownCostBasisQty. Tokens sent away stay "open" at their cost.
  • Fees inside the swap transaction count as trade cost. Tips, bot/platform fees and priority transfers paid in the same transaction are part of the price paid or received. This is right for PnL, but it can make the per-trade price (and therefore the unrealized mark) look off for tiny trades with large fees. Network fees (meta.fee) are tracked separately in swapFeesSol.
  • Token-to-token swaps (no SOL or stablecoin leg) are recorded (tokenToTokenSwaps, and in tokenToTokenSwapList with includeTrades) but excluded from PnL.
  • Not swaps: pure transfers, LP adds/removes, a token leaving while SOL also leaves (e.g. a tip bigger than the proceeds), and single-transaction arbitrage round trips. These are counted under diagnostics.unclassified by reason.
  • History depth. Results cover at most maxTransactionsPerWallet of the newest successful transactions in the window. truncated: true tells you there was more. Signature scanning stops after 30 pages (30,000 signatures).
  • Public RPC is slow and rate-limited, measured at about 2.2–2.3 s per transaction including backoff. Bring your own RPC for real work.
  • Relayed / gasless transactions (e.g. Jupiter Ultra) are handled: the wallet doesn't need to be the fee payer. Rent a relayer pays into the wallet's new token accounts appears as a small unattributedSolDelta on stable-quoted trades.
  • Only transactions that reference the wallet address are returned by getSignaturesForAddress. Tokens pushed to the wallet's token accounts without the wallet itself in the transaction are not seen. These are transfers anyway and don't affect swap detection.

Disclaimer

Results are computed from public on-chain data and are provided for research and information only. They are not financial or investment advice, and past performance of a wallet says nothing certain about future results.

Support

Found a wallet where the numbers look wrong? Open an issue on the actor's Issues tab with the wallet address and the time window, and attach the diagnostics object from the result. Maintained by northbit.