FlareWatch attribue à chaque validateur P-Chain un score composite de 0–100 réparti sur 9 dimensions. Les calculs sont déterministes, les entrées proviennent de données publiques de la blockchain [Flare Explorer] [FSE] [Flaremetrics], et le même algorithme s'applique à chaque validateur du réseau — y compris le nœud validateur de FlareWatch, qui est noté par cette fonction exacte sans traitement spécial. Cette page documente chaque dimension et seuil afin que les opérateurs et les stakers puissent voir exactement comment le score est calculé et pourquoi chaque valeur a été choisie. Chaque affirmation ici renvoie à sa source primaire on-chain ou upstream — voir Sources et références en bas.
mode staking sur la page des validateurs (délégation de FLR à un validateur P-Chain pour les récompenses VRM + MIRROR). Le score du fournisseur FTSO affiché en mode délégation (délégation de WFLR aux fournisseurs de données FTSO) utilise un algorithme distinct à 13 dimensions axé sur la performance des fournisseurs de données — précision, participation au protocole V2, etc. Ce sont des rôles on-chain distincts avec des récompenses distinctes, notés séparément. Voir Méthodologie du score du fournisseur FTSO pour le côté délégation.methodology.aprCard.p1
Comparaison avec Flare Systems Explorer ? FSE et d'autres explorateurs affichent le taux de délégation uniquement — ils n'ajoutent pas MIRROR — donc notre APY total affiche des valeurs plus élevées sur n'importe quel validateur MIRROR-actif (l'écart est exactement la ligne MIRROR ci-dessus). Les deux chiffres sont des moyennes mobiles ~8-epoch à partir des mêmes données de scripts de récompense, donc un changement de frais mi-fenêtre d'un validateur est en retard sur un snapshot de frais actuels sur l'un ou l'autre site jusqu'à ce qu'il vieillit à travers la fenêtre.
Deux autres chiffres apparaissent dans l'infobulle APY et ne sont pas le taux du délégateur : la référence théorique (APY brut du réseau × (1 − frais), staking uniquement — utilisée comme secours avant qu'assez d'historique mesuré existe), et le rendement de l'auto-obligation de l'opérateur (le rendement de sa propre mise, amplifié par la capture des frais — une métrique opérateur, pas ce que vous gagnez).
Pour le score : la dimension Rendement net score le taux de délégation VRM uniquement, et MIRROR est scoré dans sa propre dimension — donc MIRROR n'est jamais double-compté, même bien qu'il soit inclus dans l'APY total affiché.
| 90+ | Top tier — top ~10–20% des opérateurs. Profil typique : validateur full-stack + FTSO + FDC, frais bas, MIRROR-actif, fiabilité FIP-10 constante, base de délégateurs saine, auto-obligation significative. Aucune dimension unique n'est requise — les opérateurs atteignent le Top tier en accumulant la force sur la plupart des catégories. |
| 80–89 | Strong — répond à la plupart des critères clés ; une ou deux dimensions en deçà du top tier. |
| 70–79 | Good — répond à tous les critères de base ; pas de lacunes majeures. |
| 60–69 | Acceptable — utilisable mais non différencié. |
| <60 | Below median — lacunes significatives dans une ou plusieurs dimensions. Fait mathématique, pas un jugement de qualité. |
if (uptime >= 99.5) raw = 17 + (uptime - 99.5) * 6
else if (uptime >= 99) raw = 13 + (uptime - 99) * 8
else if (uptime >= 95) raw = 4 + (uptime - 95) * 2.25 // v3.9: was (u - 95) * 3.25 starting at 0
else raw = max(0, uptime - 90) * 0.8 // 90 → 0, 95 → 4 (continuity)
raw = clamp(raw, 0, 20)
reliability = epochsIncluded / epochsObserved // last ~8 epochs · v4.2: the
// FULL FIP-10 minimums set
// (uptime + FSP + FTSO + FDC),
// not RPC-uptime alone
score = raw * clamp(reliability, 0, 1) // dimension max 20scoreAPR = delegationAPY > 0 ? delegationAPY : baseAPR // VRM, net of fee medianAPR = median(scoreAPR across all validators) cappedAPR = min(scoreAPR, 25) // APY_DISPLAY_CAP if (medianAPR > 0): ratio = cappedAPR / medianAPR score = clamp(((ratio - 0.6) / 0.6) * 18, 0, 18) else: score = min(18, (cappedAPR / 8) * 18) // fallback: BASE_APY = 8
// anchor = max(observed minimum active fee, 20% protocol floor) d = fee - anchor // distance above market best if (d <= 0) score = 7 // at/below best available else if (d <= 5) score = 7 - d * 0.2 // 0 → 5 over: 7 → 6 else if (d <= 10) score = 6 - (d - 5) * 0.3 // 5 → 10 over: 6 → 4.5 else if (d <= 15) score = 4.5 - (d - 10) * 0.4 // 10 → 15 over: 4.5 → 2.5 else if (d <= 20) score = 2.5 - (d - 15) * 0.5 // 15 → 20 over: 2.5 → 0 else score = 0 // fees > anchor+20 saturate at 0/7 — the v4.5 extreme-fee // penalty below takes over from there
// Verification baseline (v3.10 hybrid) isCurated = (in KNOWN_VALIDATORS) OR ( daysObserved >= 90 AND operatorDelegatorCount >= 25 AND retention30d >= -15% AND selfBondFLR >= 1_000_000 ) if (isCurated) verification = 7 else if (name auto-discovered) verification = 3 else verification = 0 // FTSO-derived (only when ftsoOperatorScore is a number) clamped = clamp(ftsoOperatorScore, 50, 100) ftsoDerived = 4 + (clamped - 50) * (8/50) // FTSO 50 → 4, FTSO 100 → 12 // Final score score = max(verification, ftsoDerived)
passthrough = max(0, 1 - fee / 100) if (mirrorStatus == "active") base = 10 * passthrough else if (mirrorStatus == "paused") base = 5 * passthrough else if (mirrorStatus == "inactive") base = 0 else base = 5 * passthrough // no data yet // v4.6 — capped, median-anchored bonus for delivered MIRROR yield. // Only mirror-active validators paying ABOVE 1.2x the network median // delivered rate (mirrorAPY, net of fee) earn it; size-neutral. ratio = mirrorAPY / networkMedianMirrorAPY bonus = clamp((ratio - 1.2) * 5, 0, 2) // active only, else 0 score = base + bonus // dimension max 12
utilization = clamp(1 - freeSpaceFLR / maxDelegationFLR, 0, 1) if (utilization <= 0.70): score = 1.75 + (utilization / 0.70) * 5.25 // 1.75 → 7 ramp else: score = 7 - ((utilization - 0.70) / 0.30) * 1.75 // 7 → 5.25 ramp
// Count signal (max 6)
if (delegatorCount <= 5) countScore = 0
else if (delegatorCount >= 500) countScore = 6
else countScore = clamp(log(delegatorCount / 5) / log(100) * 6, 0, 6)
// Concentration adjustment (-1 to +1) — skip if <3 delegators
avgFLR = delegatedFLR / delegatorCount
if (avgFLR < 500_000) concentrationAdj = +1
else if (avgFLR < 5_000_000) concentrationAdj = +0.5
else if (avgFLR > 50_000_000) concentrationAdj = -1
else if (avgFLR > 20_000_000) concentrationAdj = -0.5
else concentrationAdj = 0
// Longevity bonus (0 to +1)
daysObserved = (now - firstObservedAtMs) / 86400000
if (daysObserved >= 90) longevityBonus = +1
else if (daysObserved >= 30) longevityBonus = +0.5
else longevityBonus = 0
// Self-bond alignment (-1 to +2, two-axis since v4.7)
if (selfBondFLR < 1_000_000) selfBondAdj = -1 // hollow-operator floor
else:
ratio = selfBondFLR / totalStake // totalStake = selfBond + delegated
proportional = ratio >= 0.10 ? +2 : ratio >= 0.05 ? +1 : 0
absolute = min(1, selfBondFLR / 20_000_000) * 2 // saturates at top-decile bond
selfBondAdj = max(proportional, absolute) // the better of the two axes
// Retention (-0.5 to +0.5, v3.7) — skip if no 30-day baseline yet
delta = (delegatedFLR - delegatedFLR30dAgo) / delegatedFLR30dAgo
if (delta >= +0.10) retentionAdj = +0.5
else if (delta <= -0.15) retentionAdj = -0.5
else retentionAdj = 0
// Self-bond trajectory (-0.5 to +0.5, v3.7) — skip if no baseline
sbDelta = (selfBondFLR - selfBondFLR30dAgo) / selfBondFLR30dAgo
if (sbDelta >= +0.20) selfBondTrajectoryAdj = +0.5
else if (sbDelta <= -0.10) selfBondTrajectoryAdj = -0.5
else selfBondTrajectoryAdj = 0
score = clamp(countScore + concentrationAdj + longevityBonus + selfBondAdj
+ retentionAdj + selfBondTrajectoryAdj, 0, 11)// Time-weighted rate is computed upstream from per-epoch // reward-scripts data with decay = 0.85 per epoch back. r = deliveryRatio // capped at 1.0 if (r >= 1.00) base = 10 else if (r >= 0.97) base = 9 + (r - 0.97) * (1 / 0.03) // 0.97 → 9, 1.00 → 10 else if (r >= 0.95) base = 8 + (r - 0.95) * (1 / 0.02) // 0.95 → 8, 0.97 → 9 else if (r >= 0.90) base = 6 + (r - 0.90) * (2 / 0.05) // 0.90 → 6, 0.95 → 8 else if (r >= 0.85) base = 4 + (r - 0.85) * (2 / 0.05) // 0.85 → 4, 0.90 → 6 else if (r >= 0.80) base = 2 + (r - 0.80) * (2 / 0.05) // 0.80 → 2, 0.85 → 4 else base = max(0, r * 2.5) // 0 → 0, 0.80 → 2 // Variance penalty (CoV = std-dev / mean across per-epoch rates) variancePenalty = min(0.30, coefficientOfVariation * 0.5) score = base * (1 - variancePenalty) // Sample-size confidence dampener for < 3 epochs if (totalStakesCompleted < 3): confidence = totalStakesCompleted / 3 score = 5 + (score - 5) * confidence
daysLeft = (endTimeMs - now) / 86_400_000 if (daysLeft < 14) score = 0 else if (daysLeft < 30) score = 0 + (daysLeft - 14) * (2 / 16) // 14 → 0, 30 → 2 else if (daysLeft < 60) score = 2 + (daysLeft - 30) * (1 / 30) // 30 → 2, 60 → 3 else if (daysLeft < 120) score = 3 + (daysLeft - 60) * (2 / 60) // 60 → 3, 120 → 5 else score = 5
consecutiveMisses = 0 for entry in participation.recent (newest-first): if entry.eligible: break consecutiveMisses++ if consecutiveMisses < 2: penalty = 0 elif consecutiveMisses == 2: penalty = 3 elif consecutiveMisses == 3: penalty = 6 else: penalty = 10 // 4+ score = max(0, positiveDimensionsSum - penalty)
// v4.5 — fees beyond the Fee dimension's range (anchor+20)
if fee <= 50: penalty = 0
else: fraction = min(1, (fee - 50) / 50) * 0.75
penalty = positiveDimensionsSum * fraction
// fee 50% → no change · 75% → −37.5% of score · 100% → −75%
score = max(0, positiveDimensionsSum - outagePenalty - penalty)delegationFee, selfBond, delegatedStake, et score FTSO (si vous êtes aussi un fournisseur de données).generated-files/reward-epoch-N/nodes-data.json. Comptez combien d'époques votre nodeID avait uptimeEligible: true. Ce ratio détermine le multiplicateur de votre dimension Uptime.RewardClaimed avec claimType=3 référençant votre nodeID. S'il n'y en a pas récemment, vous s'afficherez comme MIRROR-inactif.GET /api/validators/{nodeID}/score-breakdown pour récupérer la répartition persistante — la valeur de chaque dimension, la version d'algorithme qui l'a produite, et l'historique de score récent — en JSON. Le panneau de répartition des scores dans l'interface utilisateur lit de la même source.// Step 1 — Raw composite (sum of dimensions, minus the two penalties)
positive = uptime + netYield + fee + operatorQuality
+ mirror + capacity + trust + delivery + timeRemaining
// Penalties (see the two penalty cards above):
// v4.3 active-outage streak — 0-1 missed epochs → 0, 2 → 3, 3 → 6, 4+ → 10
// v4.5 extreme fee (>50%) — positive × min(1, (fee - 50) / 50) × 0.75
raw = clamp(positive - streakPenalty - extremeFeePenalty, 0, 100)
// Step 2 — Smooth across recent cron snapshots (v3.5)
// Weights: current 0.5, prev1 0.3, prev2 0.15, prev3 0.05
smoothed = 0.5*raw + 0.3*prev1 + 0.15*prev2 + 0.05*prev3
// Step 3 — Apply sudden-change penalty if flagged this run
// Triggers: fee +50% or +5pt jump, self-bond -20% drop, uptime -5% crash
// Magnitude: -10 pts on detection, decays -7, -4, -1 over 3 runs
suddenPenalty = -10 if any flag triggered else (decaying remainder)
// Step 4 — Final score
score = clamp(smoothed + suddenPenalty, 0, 100)services/validators/scoring.ts dans la base de code FlareWatch. Les opérateurs ou chercheurs qui veulent inspecter l'implémentation directement (plutôt que de lire la prose + formules ci-dessus) — ou qui veulent la forker pour leur propre utilisation — peuvent envoyer un email à [email protected] pour demander l'accès. Nous publierons le fichier comme package open-source autonome s'il y a une réelle demande./api/cron/refresh-validators recalcule le score de chaque validateur actif toutes les 5 minutes. Les entrées (lectures RPC P-Chain, Flaremetrics, FSE, scripts de récompenses) sont récupérées à nouveau à chaque exécution.Flare ne réduit pas le stake des validateurs. Tout le mécanisme de pénalité pour le mauvais comportement des validateurs est la confiscation des récompenses plus le système de passes FIP-10. Il n'y a pas de slash pour double signature, pas de slash pour équivoque, pas d'événement de destruction de stake que nous ayons besoin de suivre. Les validateurs qui ne respectent pas les conditions minimales de FIP-10 perdent les récompenses de cette époque (confiscation complète s'ils ont zéro pass, sinon ils perdent un pass par protocole qu'ils ont manqué) ; leur capital mis en jeu reste intact.
Cela signifie que le score n'a pas de dimension « historique de slashing » — il n'y a pas d'historique de ce type à suivre. Ce que nous SUIVONS, c'est la conséquence de chaque manquement minimum : le validateur a gagné zéro cette époque, ce qui est capturé dans epochsIncluded / epochsObserved. La mise à jour v4.2 du 14/05/2026 a élargi le multiplicateur de fiabilité du score pour utiliser ce ratio dans tout l'ensemble des minimums FIP-10 (disponibilité, signature FSP, taux de soumission FTSO, participation FDC) de sorte qu'un validateur échouant à tout minimum est pénalisé proportionnellement dans la dimension Disponibilité quel que soit l'axe échoué.
Seuils minimums FIP-10, provenant de dev.flare.network/network/fsp/rewarding : le staking nécessite 80 % de disponibilité + 1M FLR de self-bond actif ; les feeds d'ancrage FTSO nécessitent des estimations dans les 0,5 % de la médiane de consensus dans 80 % des tours ; les feeds de latence de bloc FTSO nécessitent de soumettre 80 % des mises à jour attendues ; FDC nécessite de participer à 60 % des tours de vote. Les validateurs qui respectent le plancher de 80 % de disponibilité + 1M de self-bond mais en dessous des seuils de gains de 3M / 15M reçoivent toujours des récompenses mais ne peuvent pas accumuler de passes — la zone grise mise en avant via la classification passEligibility: "at-risk" de cette carte.