how LITMUS checks
every finding is one of four things: confirmed (a source shows it), a flag (a source shows a risk, rated critical / high / medium / low), unverifiable (no free public source could answer) or a note (context, never scored). LITMUS only reads public data. it never sends a transaction and never asks for a private key.
the Litmus Score
computed by code from the findings. the AI never sees or changes it. these numbers are read from the same file the scorer uses.
- start at 100.
- penalties per flag: critical −40 · high −15 · medium −6 · low −2.
- bonuses for confirmed facts (at most +10 in total):
- +3: the project website lists this exact contract address
- +2: website and X link to each other
- +3: GitHub repository with code and more than 30 days of history
- +2: mint and freeze authority both revoked
- caps: two or more critical flags → score at most 15; any critical flag → score at most 35.
- coverage: unverifiable findings are not penalised, but when more than 50% of scored checks are unverifiable the report says low coverage next to the score.
- the result is clamped to 0–100.
the scale
- 0–20: several serious flags
- 21–40: at least one critical flag
- 41–60: many flags
- 61–80: some flags
- 81–100: few or no flags
a high score is not a safety guarantee. it means the automated checks found little — nothing more.
every check
deep = Analyst tier only · watch = re-run by the watchlist job
on-chain (Solana RPC)
| check | rule | source | scope |
|---|---|---|---|
Mint authority onchain.mint_authority | active → critical; revoked → confirmed | Solana RPC getAccountInfo | watch |
Freeze authority onchain.freeze_authority | active → critical; revoked → confirmed | Solana RPC getAccountInfo | watch |
Token program onchain.token_program | SPL Token or Token-2022 (info) | Solana RPC | watch |
Token-2022 transfer fee onchain.ext.transfer_fee | fee > 0 → high; fee > 5% → critical | Token-2022 extensions | watch |
Token-2022 transfer hook onchain.ext.transfer_hook | hook program set → high | Token-2022 extensions | watch |
Token-2022 permanent delegate onchain.ext.permanent_delegate | set → critical | Token-2022 extensions | watch |
Token-2022 non-transferable onchain.ext.non_transferable | present → critical | Token-2022 extensions | watch |
Token-2022 default frozen onchain.ext.default_frozen | default account state frozen → critical | Token-2022 extensions | watch |
Token-2022 confidential transfers onchain.ext.confidential | info | Token-2022 extensions | watch |
Token-2022 mint close authority onchain.ext.mint_close | set → medium | Token-2022 extensions | watch |
Metadata mutability onchain.metadata_mutable | mutable with update authority → medium | Metaplex / Token-2022 metadata | watch |
Metadata links onchain.metadata_links | links used by later checks (info) | off-chain metadata JSON | watch |
Supply onchain.supply | supply, decimals, burned part (info) | Solana RPC |
holders & creator (Solana RPC)
| check | rule | source | scope |
|---|---|---|---|
Top-10 concentration holders.top10 | excl. curve/pools/burn: > 30% → high; > 50% → critical | getTokenLargestAccounts | watch |
Largest wallet holders.top1 | > 10% → high | getTokenLargestAccounts | |
Creator share holders.creator_share | > 5% → medium; > 15% → high; 0 after launch buy → info | getTokenAccountsByOwner | watch |
Same-slot bundle holders.bundle | only tokens with ≤ 1000 txs; same-slot buyers' share > 10% → high, > 25% → critical | getSignaturesForAddress + getTransaction | |
Common funder holders.common_funder | ≥ 3 top-10 wallets funded by one address → high (≤ 30 RPC calls) | first incoming SOL transfer | deep |
Creator wallet age creator.age | < 24 h → medium | getSignaturesForAddress | |
Creator launch history creator.history | > 5 pump.fun launches → high (serial launcher) | getProgramAccounts on pump.fun curves (creator field) | deep |
market (DexScreener)
| check | rule | source | scope |
|---|---|---|---|
Trading pair market.pair | no pair → low; pair < 1 h → info | DexScreener | |
Liquidity market.liquidity | liquidity / mcap < 2% → high | DexScreener | watch |
Bonding curve market.bonding | graduated or progress % (info) | pump.fun bonding curve account | |
Buy / sell ratio market.buy_sell | sells > 3× buys (≥ 20 txs) → medium | DexScreener | |
Price change market.price_change | 24h drop > 80% → high | DexScreener | watch |
DexScreener profile market.dex_profile | filled or not (info) | DexScreener |
website (direct fetch, RDAP, Wayback)
| check | rule | source | scope |
|---|---|---|---|
Website reachable website.reachable | does not load → medium; no HTTPS → low | direct fetch (SSRF-safe) | watch |
Domain age website.domain_age | < 7 days → medium | RDAP (rdap.org) | |
Wayback history website.wayback | first snapshot date (info) | Wayback Machine CDX | |
CA on the site website.mentions_ca | this CA → confirmed; a different CA → high | site HTML | |
Site ↔ X website.links_x | same X as metadata → confirmed; different → medium | site links | |
Site text website.content | ≤ 20 000 chars for claim checks (info) | site HTML | watch |
X & Telegram (free public endpoints)
| check | rule | source | scope |
|---|---|---|---|
X profile exists social.x_exists | not found → low (mirror only) or medium (mirror + post oEmbed agree); no free data → unverifiable | X oEmbed + public mirror | |
X account age social.x_age | < 7 days → medium | public mirror (best effort) | |
X followers social.x_followers | info | public mirror (best effort) | |
X links back social.x_links_back | X profile links the same site → confirmed | public mirror (best effort) | |
Telegram social.tg | public page exists → confirmed (members if visible); missing → low | t.me preview page |
GitHub (REST API)
| check | rule | source | scope |
|---|---|---|---|
Repository exists github.exists | linked but missing → medium | GitHub REST API | watch |
Repository age github.age | < 7 days → medium; > 30 days → confirmed | GitHub REST API | |
Fork github.is_fork | fork with no own commits → high | GitHub REST API compare | |
Commits github.commits | < 5 commits → medium | GitHub REST API | |
Contributors github.contributors | info | GitHub REST API | |
Last commit github.last_commit | info | GitHub REST API | |
Has code github.has_code | README only → medium | GitHub REST API contents | |
License github.license | info | GitHub REST API | |
Star quality github.star_quality | > 50% of last 30 stargazers younger than 30 days → medium | GitHub REST API | deep |
claims (AI extraction, rules decide)
| check | rule | source | scope |
|---|---|---|---|
Claims summary claims.summary | AI extracts ≤ 10 claims; rules decide the status (info) | site text + AI extraction | |
Contradicted claims claims.contradicted | any claim contradicted by data → medium | rules over DexScreener / GitHub / site |
the AI's job, and its limits
- it extracts up to 10 claims from the project's own text, with a verbatim quote. claims whose quote is not in the text are dropped.
- rules decide each claim, not the AI: audits need a linked report, listings are checked against DexScreener pairs, products need a repo with code. team and partnership claims stay unverifiable.
- it writes a 3–5 sentence summary from the findings JSON only. every sentence must cite a finding id; sentences citing unknown ids are discarded.
- website text is passed as untrusted data between markers; instructions inside it are ignored. input is capped at 20 000 characters.
- when the free daily AI budget runs out, the summary is assembled by code.
sources & etiquette
- Solana mainnet RPC (Helius free tier, public endpoint as fallback); DexScreener public API; RDAP (rdap.org); Wayback Machine; GitHub REST API; X oEmbed and a public profile mirror; t.me public pages.
- project websites are fetched once per check by
LitmusBot/1.0: http/https only, ports 80/443, no private networks, ≤ 3 redirects, ≤ 2 MB, 8 s. - reports are point-in-time snapshots. recheck, compare versions, or add a token to your watchlist to follow changes.
- projects can sign with their creator or update-authority wallet to publish a response next to the report. responses never change the score.