Measure who really holds a Solana token.
Twenty holders at the top of a token can be twenty people, or three people with a lot of wallets. Qubit Orb reads those twenty wallets, finds the ones that belong together and folds them into operators. Then it tells you how far the price falls if the biggest operator sells, and what the token's owner can still change.
Everything runs in your browser against public Solana nodes. There is no backend, no wallet connect and no account. The same code that powers the scanner is published as a small module, lib/qubitorb.js, which you can import from this site and run yourself.
A measurement answers three questions: who really holds it, what happens if they sell, and what else can hurt you. Every number comes from the chain at the moment you measure. Measure again later and the answer can change.
01Quick start
On the site: paste a token address into the box on the scanner and press Measure. In code, import the module straight from this site:
import { measure } from 'https://qubitorb.vercel.app/lib/qubitorb.js'; const report = await measure('TokenMintAddress...', { onLog: l => console.log(l.t.toFixed(1), l.msg) }); console.log(report.score, report.verdict); // 62 'MIXED' console.log(report.summary.wallets, '→', report.summary.operators);
<script type="module"> import { measure } from 'https://qubitorb.vercel.app/lib/qubitorb.js'; const r = await measure('TokenMintAddress...'); document.body.insertAdjacentHTML('beforeend', `<b>${r.verdict}</b> ${r.score}/100`); </script>
# the library is a plain ES module: read it, vendor it, pin it curl -s https://qubitorb.vercel.app/lib/qubitorb.js -o qubitorb.js # the one call that needs the holder node, if you want to see it raw curl -s https://public.rpc.solanavibestation.com -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"getTokenLargestAccounts","params":["TokenMintAddress..."]}'
Node 20 and later work too: save the file as qubitorb.mjs and import it locally. It uses only fetch, atob and AbortController.
02Try it
This box runs the library from ../lib/qubitorb.js right here in the docs. Paste any token address.
03The pipeline
One measurement is seven steps. Each step only reads; nothing is signed or sent.
| # | Step | What is read | Calls |
|---|---|---|---|
| 1 | Mint | Supply, decimals, mint and freeze authority, Token-2022 extensions, name and symbol | getAccountInfo |
| 2 | Market | Price, market cap, the deepest pool and how many tokens sit in it | DexScreener |
| 3 | Holders | The 20 biggest token accounts and who owns each one | getTokenLargestAccounts, getMultipleAccounts |
| 4 | Entries | The first move of each holder account: bought or received, and from whom | getSignaturesForAddress, getTransaction |
| 5 | Funding | How many moves each wallet made before this token, where its first SOL came from | getSignaturesForAddress, getTransaction |
| 6 | Links | Proofs that two wallets are one operator | none, computed |
| 7 | Impact and score | Price drop per operator, five parts, caps, verdict | none, computed |
A fresh token is measured in about 10–20 seconds. Older tokens take longer, because their history is no longer on the fast node and has to come from the slower archive (see nodes and rate limits).
04Holders and float
Each of the top 20 token accounts has an owner. The orb looks at who owns that owner, and sorts every account into a role:
| Role | How it is recognised | Counted in float |
|---|---|---|
| wallet | Owner is a normal system account | yes |
| curve | Owner belongs to the launch curve program 6EF8…F6P. Its reserves and creator are read from the account data. | no |
| pool | Owner belongs to PumpSwap, Raydium (v4, CPMM, CLMM, LaunchLab), Meteora (DLMM, DAMM v1/v2, DBC) or Orca | no |
| locker | Streamflow or Jupiter Lock | no |
| burn | The incinerator address | no |
| program | Any other program-owned account | no |
Float = supply minus everything in the curve, pools, lockers and burn addresses found in the top 20. Every share on the site is a share of the float, because that is what can actually be sold to you.
05Entries
For every wallet, the orb reads the history of its token account and opens the oldest transaction that increased its balance:
- bought — the wallet signed and paid SOL (more than 0.003) or another token. Tokens that came out of a pool, curve or router also count as a buy.
- received — the tokens arrived without a payment. The sender is the owner whose balance dropped the most in that transaction.
- too old to read — the account has 1 000 or more moves, so its first one is out of reach.
The same transaction gives the entry slot and time. Entries within 30 seconds of creation mark a sniper.
06History and funding
The orb then reads each wallet's own history (up to 1 000 signatures):
- Fresh — the full history is visible and the wallet made 3 moves or fewer before it entered this token.
- Funder — for wallets with 150 moves or fewer, the oldest transaction is opened and the first incoming SOL transfer names the funder.
Exchange hot wallets fund thousands of strangers. A funder from the known exchange list (Binance, Coinbase, OKX, Bybit, Kraken, MEXC, Gate, KuCoin, Bitget) never links two wallets.
07Entanglement
Every proof joins two wallets with an edge. Wallets connected by edges, directly or through others, become one operator (union-find).
| Edge | Proof | Strength |
|---|---|---|
| same buy tx | Both holders got their first tokens in one transaction: a bundle | very strong |
| same slot | Both bought in the same block (~400 ms) | strong |
| one source | Both wallets got their first SOL from one address, or one holder funded another | strong |
| transfer | One top holder sent tokens to another | very strong |
| fresh-wallet pack | Three or more fresh wallets entered within 150 slots (~1 minute) | medium |
| funded by dev / sent by dev | The creator funded the wallet or sent it tokens | very strong |
Operator types: independent (one wallet), linked (two or more), pack (joined only by the fresh-pack rule), dev (contains or is funded by the creator), unread (nothing could be read).
08Price impact
Each operator sells everything at once into the market. The drop follows the constant-product rule:
drop = 1 − ( reserve / (reserve + sold) )² // on the curve: reserve = virtual token reserves, read from the curve account // after a pool: reserve = tokens in the deepest pool (DexScreener liquidity.base)
It is a scenario of what one operator could do right now, not a forecast. With no curve or pool yet, the biggest operator is judged by its share of the float instead.
09The score
A number from 0 to 100, where 100 is the cleanest distribution. Five parts, each linear between "full points at" and "zero at":
| Part | Points | Full at | Zero at | Measures |
|---|
A part that cannot be read (for example snipers when creation time is unknown) gets half its points.
10Verdicts and caps
Caps cut the score down no matter how good the rest looks:
| Cap | Score at most | When |
|---|
| Verdict | Score | Means |
|---|
There is no score when fewer than 5 wallets are in the top 20, or when fewer than 60% of the wallets could be read.
11The orb
The sphere on the scanner is a Bloch sphere: the picture physicists use for a single qubit. The top pole, |0⟩, is one owner. The bottom, |1⟩, is many. Each top holder is a dot sized by its share of the float. When a link is proven, the two dots pull together into one operator, and the purple arrow |ψ⟩ points at the biggest one.
Until you measure something, the orb plays a replay of the example token. Drag it to turn it.
12Radar and the Gate
The Radar listens to a public stream of new launches. Every launch passes the Gate before it is shown:
| Rule | Held when |
|---|---|
| Creator bought at launch | The creator bought 15% of the supply or more inside the launch transaction |
| Serial creator | The same creator launched 3 tokens or more in the last 6 hours, as seen by this page |
Held launches stay in the list in grey with the reason. The lattice shows the last 360 launches seen in your browser; gold cells reached a pool. Filters and the lattice are kept in local storage, so they survive a reload.
13Quick looks
The column next to the orb shows trending tokens with a quick look: two or three calls that read the top 20 and how much of the float the top wallets hold, without history or links. SPREAD is 30% or less, MIXED up to 50%, HEAVY above. Click one for a full measurement.
14measure()
measure(mint: string, options?: Options): Promise<Report>Runs the whole pipeline. Throws on a bad address, a non-mint address or when the holder list cannot be fetched. Pass an AbortSignal to cancel.
const r = await measure(mint, { onStep: (key, data) => ui.step(key), // mint, market, holders, float, entries, history, links, impact, score, done onHolder: h => ui.dot(h), // each wallet as soon as its entry is read onLog: l => ui.log(l.msg, l.tag) // tag: info, ok, warn, bad, dim });
const ctl = new AbortController(); setTimeout(() => ctl.abort(), 30000); try { await measure(mint, { signal: ctl.signal }); } catch (e) { if (ctl.signal.aborted) console.log('cancelled'); }
15quickLook()
import { quickLook } from 'https://qubitorb.vercel.app/lib/qubitorb.js'; const q = await quickLook(mint); // { wallets: 18, top1: 0.027, topShare: 0.35, inCurveOrPool: 0.11, mintOpen: false, freezeOpen: false, look: 'MIXED' }
16Options
| Option | Default | What it does |
|---|---|---|
rpc | publicnode | Fast node for accounts, signatures and transactions (~2.5 days of history) |
holdersRpc | solanavibestation | Node for getTokenLargestAccounts and the full-history fallback |
archive | [] | Extra full-history lanes { url, gap }, used together with holdersRpc |
top | 20 | Holders read |
concurrency | 6 | Parallel calls to rpc |
holdersGap | 450 | Milliseconds between calls to the slow node |
sniperWindow | 30 | Seconds after creation that count as sniping |
freshTxs | 3 | Moves before entry that still count as fresh |
packSlots | 150 | Window for a fresh-wallet pack |
timeout | 15000 | Per-request timeout, ms |
signal, onLog, onStep, onHolder | — | Cancel and progress hooks |
Running many measurements? Bring your own RPC: pass rpc and holdersRpc with your key and the slow lane disappears.
17Report format
{
mint, version, ms,
score: 0..100 | null,
verdict: 'COHERENT' | 'MIXED' | 'ENTANGLED' | 'UNMEASURED',
unmeasured: string | null, // why there is no score
token: { program, decimals, supply, mintAuthority, freezeAuthority, permanentDelegate,
transferFee, transferHook, pausable, name, symbol },
market: { priceUsd, marketCap, liquidityUsd, liquidityBase, dex, pair, url, createdAt } | null,
curve: { vToken, vSol, realToken, realSol, complete, creator, progress } | null,
float, creator, devHolds, devShare, createdAt,
holders: [{ rank, account, owner, role, amount, share, floatShare,
entry: { kind, from, slot, time, sig }, moves, historyFull, fresh, sniper,
funder: { from, lamports, time }, operator, opType }],
edges: [{ a, b, why }],
operators: [{ id, members, size, amount, floatShare, type, why, drop }],
parts: [{ key, label, points, full, zero, value, got }],
caps: [{ key, cap, text }],
summary: { wallets, operators, entangled, freshShare, recvShare, sniperShare, biggestDrop, biggestShare, powers },
notes: [string]
}18Constants
The module exports everything it decides with, so nothing is hidden:
| Export | Contents |
|---|---|
RULES | Score parts, caps and verdict thresholds, the same tables shown on this page |
PROGRAMS | Program ids used to recognise curve, pools, lockers and token programs |
EXCHANGES | Exchange hot wallets that never link wallets |
DEFAULTS | Default options |
priceDrop(sold, reserve) | The impact formula |
partPoints(value, part) | Linear points for one score part |
isAddress(s), short(a) | Helpers |
19Errors
| Message | Meaning | What to do |
|---|---|---|
| That is not a Solana address. | The input is not base58, 32–44 characters | Paste the mint address, not a pair or a link |
| This address is not a token mint. | It is a wallet, pool or program | Use the token's contract address |
| The holder list is busy right now. | The holder node kept answering 429 | Wait a minute, or pass your own holdersRpc |
| verdict UNMEASURED | Too little could be read | Read report.unmeasured, measure again |
20Nodes and rate limits
The free public nodes each answer some questions and refuse others, so the orb splits the work:
- publicnode answers fast and in parallel, but keeps only about 2.5 days of history and caps
getMultipleAccountsat 10 accounts. - solanavibestation answers
getTokenLargestAccountsand keeps the full history, but only about two calls a second. All of its calls go through one spaced, retried queue.
When the fast node's history of an address ends near its oldest slot, the orb asks the archive for that address. That is why fresh tokens are fast and old ones are slow.
21What it cannot see
- Wallets outside the top 20. A careful operator can spread below the line.
- Exchange-funded clusters. Two wallets funded from one exchange account look independent, on purpose.
- Third-party hops. A link through a wallet the orb never read stays invisible.
- Very busy accounts. 1 000 moves or more hide the first entry and the funder.
- The future. A good score today says nothing about tomorrow. Measure again.
It never asks for a wallet, a signature or a key, and stores only your own recent measurements and Radar filters in your browser.
22$QUBIT
$QUBIT is the token of this scanner. You can paste its own address into the orb: it will not go easy on itself.
23FAQ
Is a COHERENT token safe?
It means the top holders look like different people right now. It does not mean the price will hold.
Why does an old token take a minute?
Its history is past the fast node's window, so every wallet goes through the slow archive queue. Fresh tokens finish in seconds.
Why is a wallet "too old to read"?
Its token account has 1 000 or more moves, so the first one is beyond a single page of history.
Can I run it on my own node?
Yes. Pass rpc and holdersRpc to measure(). With a paid node the whole measurement takes a few seconds.
Does the site store anything?
Only in your browser: your last measurements, quick looks, Radar filters and the launches it has seen. Nothing leaves your machine except the read calls to public nodes.