HP scoring ▼
filter.fun’s HP score determines who survives the cut and who gets filtered. The numbers aren’t arbitrary — every weight is empirically fit against historical token launches, every formula constant is named in source, and the engine is a pure function of the indexer’s input bundle. This page documents the scale, the components, the locked constants, the methodology, and how to verify the live engine matches the spec.The HP composite
HP is a 0–10000 integer. Computed as the weighted sum of six components, each on a 0-1 scale, multiplied by 10000 and rounded.- Matches the basis-point convention used throughout filter.fun (fee splits, settlement math, HHI computation)
- Integer storage eliminates float-precision bugs in rank comparisons at the cut threshold
- Same effective resolution as 0–100 with two decimal places (10000 discrete values either way) but cleaner display and JSON
- Aligns with “HP” as a high-score-style integer (cultural convention from games)
- HP descending (primary)
launchedAtascending (earlier-launched wins)- Token address ascending (lower-hex wins — final tier)
The six components
| Component | Weight | What it measures | Output |
|---|---|---|---|
| velocity | 30% | Decayed net buy inflow with absolute per-wallet cap and churn penalty | clip(raw / VELOCITY_REFERENCE, 0, 1) |
| effectiveBuyers | 15% | Σ sqrt(walletBuyVolume) above dust — sqrt-dampened economic significance | clip(raw / EFFECTIVE_BUYERS_REFERENCE, 0, 1) |
| stickyLiquidity | 30% | LP depth penalized by withdrawals decayed exp(-Δt / 6h) over 24h | clip(raw / STICKY_LIQUIDITY_REFERENCE, 0, 1) × ageFactor |
| retention | 15% | Two-anchor holder intersection with dust-supply filter and ageFactor | ratio × ageFactor (already 0-1) |
| momentum | 0% | Bounded recent acceleration (disabled under v4 lock) | gated by HP_MOMENTUM_ENABLED |
| holderConcentration | 10% | HHI on excluded-address-filtered holder balances, log-mapped | (1 − hhi) × ageFactor (already 0-1) |
clip(raw / *_REFERENCE, 0, 1). References are calibrated from the Track E v5 cohort 90th percentile; the same input always produces the same component score, regardless of cohort composition. Pre-1.22 the engine used cohort percentile rank — the lock replaces this with absolute references so a weak cohort no longer mass-medians to the middle of the scale.
Why these six (and not others)
Three principles shaped the active component list:- No raw market cap. Market cap is gameable by a single whale buy at thin liquidity. HP rewards real demand, not price action.
- No raw unique-buyer count. Sybil farms can manufacture wallet count cheaply. The sqrt-dampening on
effectiveBuyersplus the per-wallet cap onvelocityand the HHI penalty onholderConcentrationmake sybil construction quadratically expensive. - No social-signal data inside HP. Social volume is high-leverage but un-verifiable on-chain and breaks HP’s deterministic, auditable property. Social data lives on spectator surfaces (planned Phase 3) but doesn’t influence eliminations.
Locked formula constants
Every parameter the formula bodies depend on lives in named, typed, exported constants in the open-sourcescoring/ package. Formula bodies reference these by name; zero magic numbers. A change to any constant follows the same ≥7-day public-notice procedure as a weight change.
| Constant | Value | What it controls |
|---|---|---|
VELOCITY_LOOKBACK_SEC | 345 600 (96h) | Hard window for velocity event filter |
VELOCITY_DECAY_HALFLIFE_SEC | 86 400 (24h) | Per-event time-decay half-life |
VELOCITY_PER_WALLET_CAP_WETH | 10 | Single-wallet contribution cap (anti-whale) |
VELOCITY_CHURN_PENALTY_FACTOR | 2 | Churn-detected sell penalty multiplier |
VELOCITY_REFERENCE | 1115.451 | Fixed-reference normalizer (v5 90th p) |
EFFECTIVE_BUYERS_DUST_WETH | 0.001 | Min in-window spend to count as a real buyer |
EFFECTIVE_BUYERS_REFERENCE | 191.129 | Fixed-reference normalizer |
LP_PENALTY_WINDOW_SEC | 86 400 (24h) | Sticky-liquidity LP-removal penalty window |
LP_PENALTY_TAU_SEC | 21 600 (6h) | Penalty exponential decay constant |
STICKY_LIQUIDITY_REFERENCE | 67.275 | Fixed-reference normalizer |
RETENTION_DUST_SUPPLY_FRAC | 0.0001 | Min holder share for retention denominator |
SETTLEMENT_FINALITY_BLOCKS | 12 | CUT/FINALIZE wait before snapshot insert |
/scoring/weights under response.constants so an external auditor can fetch the live engine’s parameters directly. Drift between the endpoint response and the published constants is a deploy bug, not an ambiguity.
Per-component formulas
Velocity — decayed net inflow
Effective buyers — sqrt dampening
sqrt dampening means 30 wallets at 1 WETH outscore one whale at 1000 WETH on real-buyer signal.
Sticky liquidity — exponential LP-removal penalty
Δt = 0 contributes its full amount; at Δt = 6h contributes ~37%; at 24h almost nothing. ageFactor saturates at 1.0 once the token has been observed for ≥1h — prevents a brand-new token from instantly claiming full sticky-liquidity credit before any time-weighted depth has accrued.
Retention — two-anchor + dust filter
Holder concentration — HHI
HP_CONCENTRATION_ENABLED (default true); when off, the remaining five weights renormalize to sum to 1.0.
Momentum — bounded recent acceleration (disabled under v4 lock)
Disabled under the current lock (weight = 0, HP_MOMENTUM_ENABLED = false). Code path retained for a future v6 revival; producers should keep storing priorBaseComposite for that purpose.
How we derived the weights
The weights aren’t theoretical. They’re the output of an empirical research track called Track E that ran across six iterations, each on real on-chain corpora, with statistical analysis and cross-validation against external research.The story arc
| Run | n | Headline finding |
|---|---|---|
| v2 | 48 | First real signal across the 5 baseline components; holderConcentration validated as the candidate 6th component (positive predictive signal, no rank disruption) |
| v3 | 250 | Stratification gaps + outcome-label leakage flags surfaced; momentum input bug diagnosed (98% zero) |
| v4 | 100 stratified | L2 fit assigned momentum 0% on every outcome label across a balanced corpus; stickyLiquidity confirmed as dominant non-leakage signal; weights LOCKED |
| v4 validation | 7 | Random-sample-then-FDV funnel returned too small a cohort; v5 algorithm fix filed |
| v5 | 43 | Liquidity-first scan algorithm; LOCKED weights ρ +0.364 (p=0.016, significant — validated) |
| v5 formula-lock refit | 43 | Locked formulas + fixed-reference normalization → ρ +0.421 (p=0.005), +0.057 above pre-lock baseline; tolerance band confirmed |
Validation summary
| Weight set / engine | n | Spearman ρ | p-value | Verdict |
|---|---|---|---|---|
| LOCKED + formula-lock (current) | 43 | +0.421 | 0.005 | active |
| LOCKED, pre-formula-lock (v5 reference) | 43 | +0.364 | 0.016 | reference |
What the methodology actually does
For each Track E run:- Build a stratified corpus. Pull a balanced sample of survivor + dead tokens from Clanker V4 launches. v4 used 50/50 dead/survivor (n=100); v5 used a liquidity-first scan against PoolManager Swap logs to recover top-FDV tokens (n=43).
- Compute HP components for each token at hour 96 post-launch using the production scoring code.
- Compute outcome labels the components don’t read —
survived_to_day_7,holder_retention_at_30d,price_floor_at_30d. The decoupling matters: if outcomes derive from data the components also read, fitted weights are inflated by leakage. - Fit weights via L2-regularized logistic regression against each outcome label. Compare to defaults.
- RandomForest feature importance as a non-linear sanity check.
- Leakage discipline — flag any component-vs-outcome correlation with |ρ| ≥ 0.85; treat the corresponding fitted weights as inflated, don’t act on them.
- Lock decision — adopt only fitted weights that survive the leakage check, have non-trivial empirical signal, and don’t disrupt rank stability vs current weights.
What the data said about each component
stickyLiquidityis the dominant predictor. v4 RF importance 0.38 (highest of any component); v5 Spearman ρ +0.87 against price-floor outcome. Empirical weight in the L2 fit was 42%, but the leakage flag (component and outcome share underlying LP data) means that’s an inflated upper bound. Locked at 30% — a conservative middle ground between the prior default (20%) and the inflated fit (42%).retentionis a real but leakage-suspect signal. v4 ρ +0.93 against the same-name outcome label hit the leakage threshold. Held at the prior default 15%.holderConcentrationvalidated as a real component across two runs (v2 ρ +0.46 / AUC 0.80; v3 ρ +0.39 / AUC 0.61). Adding it doesn’t disrupt rankings (rank stability ρ +0.997 to +1.000). Locked at 10%.velocityandeffectiveBuyersare weaker than prior defaults suggest. v4 L2 fit assigned 19% and 20% (vs 30% and 15% prior). With n=100 and known leakage in the outcome that drove the fit, deviating on these would be over-fitting. Held at defaults: 30% and 15%.- A sixth component,
momentum, was tested and removed. The L2 fit assigned momentum 0% on every outcome label across a 100-token stratified corpus. Diagnostic showed 42% of survivor-half tokens had structurally flat HP in the 72h→96h window where momentum is measured. The component was structurally degraded by the current delta-window definition. Removed; its 10% weight slot was redistributed tostickyLiquidity(the strongest non-leakage signal). The component’s code remains in place behind a feature flag (HP_MOMENTUM_ENABLED=false) for v6 reconsideration if a wider delta window surfaces signal.
Cross-validation against published research
The most credibility-bearing part of the methodology is that filter.fun’s empirical findings independently match results from external research on adjacent launchpad ecosystems. Marino, Naviglio, Tarantelli, Lillo (2026) — “Predicting the success of new crypto-tokens: the Pump.fun case” (arxiv 2602.14860) — a peer-reviewable analysis of n=567,876 pump.fun tokens. Their three headline findings, mapped to filter.fun’s components:| Marino finding | filter.fun equivalent | Status |
|---|---|---|
| ”Fast accumulation of liquidity through a small number of trades is the strongest predictor of graduation” | stickyLiquidity as dominant component | Cross-validated |
| ”Markets dominated by bot-like activity exhibit systematically lower graduation probabilities” | holderConcentration HHI penalty + effectiveBuyers sqrt-dampening | Cross-validated, design vindicated |
| ”Presence of historically successful traders provides at most a modest and non-monotonic effect” | (we considered cross-token wallet history, didn’t model it) | Confirms our scope |
Settlement finality + reorg safety
EveryhpSnapshot row carries a finality tag:
| trigger | initial finality | progression |
|---|---|---|
BLOCK_TICK, SWAP, HOLDER_SNAPSHOT, PHASE_BOUNDARY | tip | tip → soft (+6 blocks) → final (+12 blocks) |
CUT, FINALIZE | final (by construction) | n/a — writer waits ≥SETTLEMENT_FINALITY_BLOCKS past the wall-clock boundary before insert |
finality = "final" and refuses to post otherwise. A reorg inside the [soft, final) window therefore can’t put settlement at risk — only the observability surface (web UI + API) might briefly show stale tip data, which is recoverable on the next HpFinalityAdvancer tick (~12s on Base). The advancer is monotonic in the finality lattice — final rows never demote.
Wallet attribution: tx.from, not event.args.sender
Indexer swap rows attribute to event.transaction.from — the EOA that signed the transaction — not event.args.sender. Under V4 hooks, sender is the pool manager / universal router contract; using it would bucket every trader into the same hot wallet and zero out the per-wallet velocity / effective-buyers signal. tx.from correctly resolves to the user’s wallet for direct + universal-router calls (>99% of expected traffic). The remaining edge case (meta-tx relayers where a separate party pays gas) needs full router decoding and is filed as future work.
How to verify
Two concrete actions for an external auditor: 1. Pull live weights + constants from the API.version, weights, constants, flags, compositeScale. Cross-check version against the active value below and the constants table above.
2. Run the locked fixture suite from the open-source scoring/ package.
35+ fixtures (≥5 per component + 10 composite) load deterministic (input, expected) pairs and assert the engine produces byte-identical output. Every fixture is tagged with weightsVersion and the runner asserts the tag matches the active HP_WEIGHTS_VERSION — a version bump without fixture updates fails CI.
Trust model
- Weights live off-chain in the
scoring/package config — not in contract bytecode. Contracts only see the oracle-published Merkle root of rankings; they never read weight values directly. - Engine is a pure function of the indexer’s input bundle. The
inv_hp_formula_pureruntime invariant callsscore()100× across varied chain states and asserts byte-identical output. - Settlement-tagged rows are reorg-immune by construction (≥12-block wait) + verified by the
inv_hp_settlement_finalityinvariant before any oracle Merkle publish. - Weight + constant changes follow ≥7-day public-notice procedure.
- Every
hpSnapshotrow is tagged withweightsVersionfor historical reproducibility; an auditor can replay any past ranking under the exact engine that produced it.
Honest caveats
The pre-formula-lock v5 cohort showed pre-v4 defaults (which kept momentum at 10%) edging the LOCKED weights by Δρ ≈ 0.045 on n=43. The 1.22 formula-lock refit reproduces the LOCKED weights at ρ = +0.421 (p = 0.005), +0.057 above the pre-lock baseline — meaning under fixed-reference normalization the locked weight set is now stronger than the pre-v4 defaults were under the legacy normalization. The formula lock didn’t just preserve the v5 finding; it improved on it. The momentum coefficient remains the most-fragile call in the locked weight set. A v6 / post-mainnet refit will revisit it once filter.fun has approximately 30+ days of its own corpus data.What’s next
Post-mainnet refit: once filter.fun has approximately 30+ days of its own corpus, weights and reference constants will be refit against both stratified and recent-active cohorts. The momentum coefficient will be revisited at that point — if a wider delta window (96h − 48h vs current 96h − 72h) surfaces signal, momentum may be revived at 5-10% weight. If not, the 5-component composition stays. Other deferred work (non-blocking):- Per-event
lpEventstimeline for sticky-liquidity (engine has aggregate fallback today; per-event LP requires V4 ModifyLiquidity wiring) - Transfer event log for exact retention-anchor reconstruction (current
firstSeenAtapproximation is the documented compromise) - Router-decoded EOA for meta-tx relayers (
tx.fromcovers >99% of expected traffic) - Marino cross-check on a stratified validation cohort (current cohort structurally can’t populate Marino’s failure cell)
HP scoring is the load-bearing mechanic of filter.fun’s tournament. It’s empirically grounded, cross-validated, formula-locked at named constants, and runtime-revisable as the protocol learns from its own data. Cross-links: Settlement security model · Audit status · Weights endpoint