npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

solana-risk-engine

v0.5.4

Published

Explainable risk analysis for Solana tokens and wallets: creator control, real holder concentration, inflated liquidity, impostor tokens, funder clusters and PnL. Zero dependencies, runs in Node and the browser.

Readme

solana-risk-engine

Explainable risk analysis for Solana tokens and wallets. Zero dependencies, runs in Node 18+ and the browser, works with free public RPCs out of the box.

It doesn't stop at a single score. Every verdict comes with the signals behind it, as stable codes plus the numbers involved. Apps can show them in any language (Spanish and English are included).

import { createScanner, explainLight, lightLabel, levelLabel } from "solana-risk-engine";

const scanner = createScanner();                      // free PublicNode + Vibe Station, or { rpcUrl }
const r = await scanner.analyzeToken("7xKX...gAsU");

console.log(r.lvl);        // "HIGH"
console.log(r.type);       // "CREATOR"
for (const l of r.lights)  // the four traffic lights
  console.log(l.s, lightLabel(l.k, "en"), "-", explainLight(l, "en"));
// bad  Creator control - The creator can mint more tokens (mint authority active)
// bad  Concentration   - The largest individual wallet holds 50% and the top 10 hold 60% of the supply
// bad  Market and exit - Very low liquidity ($5,000). Very new market (3 days)
// ok   Dependency      - Does not depend on an issuer or a bridge

What it detects

| Check | How | |---|---| | Creator control | Mint/freeze authority still active, and whether it sits in a person's wallet or a program (bridge, protocol, staking pool). A key off the ed25519 curve is always a program (PDA), even when the System Program owns its account. Risky Token-2022 extensions: permanent delegate, transfer hook, transfer fee, non-transferable, default-frozen accounts. | | Real concentration | Largest accounts are traced to their owners. Pools and programs are separated from people, so w1/w10 measure people, not AMM vaults. | | Inflated liquidity | DexScreener values both sides of a pool at the pool's price. A pool with 4B of a token against 2 USDC shows "$4,000M liquidity". The engine recomputes it from the real side (USDC/USDT/SOL). It only flags extreme gaps (real side under 5%), because concentrated-liquidity pools are lopsided by nature. | | Unlocked LP | % of LP locked/burned via RugCheck. It only counts for markets under 90 days old, so veteran tokens don't get false positives, and never while a token is on Pump.fun's bonding curve, where there is no LP to pull. | | Serial creators | For markets under 7 days: how many other tokens the creator launched in the last 24 h (RugCheck). 2 or more is a warning, 5 or more a red flag. recentLaunches(client, wallet) counts the same on chain for the wallet that paid a launch, which catches farms that name a fresh creator each time. When RugCheck has no report yet (it often answers 400 for tokens a few minutes old) the analysis falls back to chainLaunches(client, mint): that count for the first signer of the token's creation transaction, reported as its own signal CTL_SERIAL_PAYER ("the wallet that paid for this launch…", never as the creator) and skipped when the SolFlag wallet base knows that wallet as an exchange or platform. | | Impostors | Tokens using the symbol or name of a famous token (USDC, JUP, BONK, WIF, BTC, ETH...) with a different address. | | Real brands | About 320 real companies with no token on Solana (Spotify, Nike, Zara, Ferrari...). A token called just that is a red flag and HIGH; the brand as one word of a longer name is a warning. Everyday words that are also brands (Shell, Target, Puma) are left out, and markets at least 30 days old with $100k+ liquidity are not checked. | | Known issuers and official tokens | Stablecoins from Paxos (PYUSD, USDG), Circle (USDC, EURC) and Tether (USDT), Ondo's USDY, OUSG and USDon, tokenized shares from xStocks (all recognised by their control keys, taken from mints the issuer lists on its own pages) and official tokens from exchanges and Socios.com (bnSOL, bbSOL, cbBTC, club fan tokens..., by mint address) are shown as dependency on the issuer. Their large holders (treasury, custodians, exchanges) are shown but do not count as concentration, a 0% transfer fee is not a warning, and a tiny Solana pool is a warning, not a red flag. A short trusted list (Circle, Paxos, Tether, xStocks; trustedIssuer()) goes further: every light green, holders not checked. If one of an issuer's official mints shows a control key that is not on the list (a key rotation, or a stolen key), it keeps the issuer but turns amber (CTL_ISSUER_KEYS_CHANGED, issuerKeysChanged()) until the key is reviewed; setIssuers(list) swaps in a reviewed list without a new release. A known issuer's token with no public market (sold only to approved investors, like OUSG) is "no market", never spam. | | Token type | STABLE, TOKENIZED, BRIDGED, CREATOR, OPEN. Each type is judged by the rules that fit it: a bridged token normally has a program as mint authority, a memecoin does not. | | Bots and exchanges | Wallets that made 200 transactions in under 2 days are marked active and are not counted as whales. | | Funder clusters | The fee payer of each holder's first transaction reveals who created it. Holders sharing a funder are grouped, since that is often one team splitting the supply. Exchange hot wallets are excluded as sources. | | Creator history | Other tokens by the same creator and their risk, rugged flag, and insider networks with today's on-chain balances (RugCheck). | | Wallet PnL | FIFO realized and unrealized PnL from the last ~300 swaps, valued with historical SOL prices (Helius + CoinGecko). | | Confluence | Tokens held in common by a group of wallets, each one risk-assessed. Also the reverse: people who are top holders of several tokens. | | Change tracking | Snapshots and diffs (new / bought more / sold part / sold all) per wallet and aggregated across many wallets. |

How the level is decided

  • Two red lights, or one red and one yellow, give HIGH.
  • One red, or two yellows, give MEDIUM.
  • Otherwise the level is LOW, with two exceptions that raise it to MEDIUM:
    • A check could not run. Lights that could not be checked are marked na, and they never count as "fine". dataOnly(r) tells you when this is the only reason.
    • The market is under 7 days old. It has no track record yet.
  • An impostor of a famous token, or a token named just like a real company, is always HIGH.

A single yellow on an established token stays LOW, because those warnings are normal for its type (a staking pool's program-controlled mint, for example). This was measured on 62 real tokens: the 7-day rule moved 9 brand-new tokens from low to medium and no established one.

Install

npm install solana-risk-engine

API

createScanner(options)

| Option | | |---|---| | rpcUrl | Your own RPC (Helius, Triton, QuickNode...). Without it, PublicNode is used, with Solana Vibe Station (rate-limited queue) for the methods PublicNode blocks. | | heliusApiKey | For PnL. Taken from rpcUrl automatically when that is a Helius URL. It is never sent anywhere else. | | signal | An AbortSignal. Aborting rejects pending work with ScanError code CANCELLED. | | fetch | Custom fetch (tests, proxies). |

Methods (all accept { onProgress(step, fraction) }):

| Method | Returns | |---|---| | analyzeToken(mint, { skipCreator }) | { lvl, sev, type, lights, reasons, impostor, pair, creator, ... } | | analyzeWallet(wallet, { minUsd }) | { sol, balances, total, hidden, results: [token results] } | | analyzeAddress(addr) | { kind: "token" \| "wallet", ... }, detected automatically | | analyzeHolders(mint) | { rows, clusters, groups, hotGroups, creator, mode, ageDays, signals, ... }. Young tokens (under 14 days) also trace busy wallets; a source over 100 tx/h counts as an exchange; a group over 10% (young) or 15% (mature) of the supply gives a HOLD_GROUP signal. With { base: true } it also reads SolFlag's wallet base (exchanges, launch farms, distributors and serial creators its collector has seen): known exchanges are recognised without measuring them, and rows and group sources get known / knownSources. Best with your own RPC. | | loadWalletBase([url]) | SolFlag's wallet base { wallets: { addr: { t, ph, n, roles } } }, or null if it cannot be read. Kept 10 minutes. knownType(base, addr) gives a wallet's type. | | knownWallet(addr) | { n, t } for a wallet with a public name (exchange hot wallets of Binance, Coinbase, OKX, Bybit, Kraken and others, each checked against its Solscan label), or null. Concentration leaves these exchanges out of the count and names them (ex, exn in the signal data). | | analyzeHolders(mint) | { rows, clusters, groups, hotGroups, creator, mode, ageDays, signals, ... }. Young tokens (under 14 days) also trace busy wallets; a source over 100 tx/h counts as an exchange; a group over 10% (young) or 15% (mature) of the supply gives a HOLD_GROUP signal. Best with your own RPC. | | connectionMap(mint, { createdAt }) | { mode, holders, sources, groups, nets, hub, creator, payer, signals }: largest holders typed person / bot / exchange (over 100 tx/h) / pool, who gave each one its first SOL, groups sharing that source, RugCheck's token-transfer networks with their links. young (under 14 days) or mature decides what is traced and when it warns (MAP_GROUP, MAP_SWARM for 10+ holders from one source whatever they hold, MAP_HUB, MAP_WHALE). 20 holders, 75 with your own RPC. | | estimatePnl(wallets) | { pnl: { [wallet]: { realized, unreal, total, ret, st, open } } }. Needs Helius. | | takeSnapshots(wallets) | Today's holdings. Store them yourself and pass them to diffSnapshots(prev, next). | | confluence(wallets, { pct, withPnl, pnlFilter }) | Tokens held in common, with risk. | | commonHolders(mints, { min }) | Wallets that are top holders of several tokens. |

Every lower-level function is exported too (buildLights, levelFrom, realLiq, clusterByFunder, chgDiff, pnlFromTrades...). Most are pure functions that are easy to reuse and test.

Results

{
  lvl: "HIGH" | "MEDIUM" | "LOW" | "SPAM" | "NO_DATA" | "NO_MARKET",
  type: "STABLE" | "TOKENIZED" | "BRIDGED" | "CREATOR" | "OPEN" | null,
  lights: [                       // null for tokens without a market
    { k: "control" | "concentration" | "market" | "dependency",
      s: "ok" | "na" | "warn" | "bad",
      items: [{ code: "MKT_LIQ_INFLATED", s: "bad", data: { reported: 4e9, real: 2 } }] }
  ],
  reasons: [ ...every non-ok signal ],
  sev: 6                          // for sorting within a level
}

Texts and languages

import { describe, explainLight, levelLabel, typeInfo, describeStep, describeError, addLanguage } from "solana-risk-engine";

describe({ code: "MKT_LIQ_VERY_LOW", data: { liq: 3000 } }, "es");   // "Liquidez muy baja (3.000 $)"
describeError(err, "en", { ownRpc: true });                          // friendly message for any ScanError
addLanguage("pt", { signals: { ... }, levels: { ... } });            // same shape as src/i18n/en.js

Errors

The engine throws ScanError with a stable code: CANCELLED, HTTP, NETWORK, RPC, DEX_UNREACHABLE, HELIUS_REQUIRED, SOL_PRICE, INVALID_ADDRESS, NOT_A_TOKEN, IS_NFT, NO_HOLDERS, TOO_FEW_WALLETS, TOO_FEW_TOKENS.

Optional extras (RugCheck) never throw. If they fail, the analysis goes on without them.

CLI

npx solana-risk <token-or-wallet> --lang en
npx solana-risk holders <mint> --rpc "https://mainnet.helius-rpc.com/?api-key=..."
npx solana-risk <mint> --json

Data sources

Development

npm test      # node --test, no network needed: the network is mocked

Disclaimer

This is a heuristic tool, not financial advice. A "low risk" result means the engine found none of the signals it looks for. It does not mean the token is safe.

License

MIT