Scoring Methodology

How SatRank computes reliability scores for Lightning endpoints

← Back to SatRank

Overview

SatRank produces a composite trust score between 0 and 100 for each agent, computed from 5 independently measurable components. The score is deterministic: the same inputs always produce the same output. All data sources are verifiable through public URLs.

Components and Weights

The final score is a weighted sum of 5 components, each scored 0–100:

score = volume * 0.25 + reputation * 0.30 + seniority * 0.15 + regularity * 0.15 + diversity * 0.15
ComponentWeightMeasures
Volume 25% Transaction throughput or channel count
Reputation 30% Graph centrality, peer trust (BTC/channel), attestations
Seniority 15% Time on the network since first observation
Regularity 15% Consistency of activity over time
Diversity 15% Breadth of counterparties or capacity

Formulas

Volume (Lightning nodes)

Logarithmic scale with a fixed reference of 500 channels. Spreads the full 0–100 range across the real network distribution:

volume = min(100, round(log(channels + 1) / log(501) * 100))

5 channels: ~26. 20: ~48. 50: ~63. 100: ~74. 500+: 100.

Volume (Observer Protocol agents)

Logarithmic scale normalized to a reference of 1000 verified transactions:

volume = min(100, round(log(count + 1) / log(1001) * 100))

Reputation (Lightning nodes)

Combines two objective, on-chain signals: graph centrality and peer trust.

// Centrality (max 50 pts) — position in the network graph
centrality = 25 * exp(-hubnessRank / 100) + 25 * exp(-betweennessRank / 100)

// Peer trust (max 50 pts) — how much BTC others lock in channels with you
btcPerChannel = capacitySats / 100_000_000 / channels
peerTrust = min(50, round(log10(btcPerChannel * 100 + 1) / log10(201) * 50))

reputation = min(100, centrality + peerTrust)

Centrality measures how well-connected a node is in the Lightning graph. Peer trust measures how much real capital other nodes commit to channels with you — skin in the game. Neither signal depends on external platforms or user ratings.

LN+ community ratings (from lightningnetwork.plus) contribute a separate bonus of up to +8 points on the final score. This keeps the social signal without making 30% of the score depend on a third-party platform.

Reputation (Observer Protocol agents)

Weighted average of attestation scores with exponential time decay (half-life: 30 days). Each attestation's weight is multiplied by the attester's own score, normalized to [0, 1]. See Anti-Gaming for penalty mechanisms.

When no attestations exist yet, reputation is 0 and its 30% weight is redistributed to the other 4 components (volume * 0.36 + seniority * 0.21 + regularity * 0.21 + diversity * 0.21). This allows new agents to score fairly based on their transaction history alone, while attestations build over time.

Seniority

Exponential growth curve with diminishing returns (2-year half-life):

days = (now - firstSeen) / 86400
seniority = round(100 * (1 - exp(-days / 730)))

30 days: ~4. 6 months: ~22. 1 year: ~39. 2 years: ~61. 3 years: ~78. 5 years: ~93.

Regularity (Lightning nodes)

Multi-axis consistency over 7 days. A node that is always reachable but whose latency varies wildly or whose routing paths flap is less reliable than one that is rock-steady, and the score reflects that. Pure uptime alone used to saturate at 100 for ~77% of scored agents and stopped differentiating the top cluster.

// Primary: multi-axis (requires >= 3 probes in 7 days)
regularity =  uptime_ratio        * 70   // reachable / total probes
            + latency_consistency * 20   // exp(-stddev/mean) across reachable latencies
            + hop_stability       * 10   // 1 - clamp(stddev_hops / 3, 0, 1)

// Fallback: gossip recency (when < 3 probes available)
regularity = min(100, round(100 * exp(-daysSinceGossip / 90)))

Stable, fast, rock-steady: ~100. 100% uptime with noticeable latency jitter: ~85. 80% uptime with stable routes: ~86. 50% uptime: ~65. Dead: 0.

Regularity (Observer Protocol agents)

Inverse coefficient of variation (CV) of transaction intervals. Requires at least 3 transactions. Regular intervals score high; erratic patterns score low.

cv = stddev(intervals) / mean(intervals)
regularity = min(100, round(100 * exp(-cv)))

Diversity (Lightning nodes)

Number of unique peers (distinct nodes sharing a channel), extracted from the validated LND graph. A node with 500 BTC concentrated on 2 peers offers no routing diversity; a node with 0.5 BTC spread across 20 peers is a real contributor to network health. Peer count is the real signal; capacity is a correlated but weaker proxy.

diversity = min(100, round(log(unique_peers + 1) / log(501) * 100))

1 peer: ~11. 10 peers: ~38. 50 peers: ~63. 200 peers: ~85. 500+ peers: 100.

Falls back to capacity-based scoring only for fresh agents the LND crawler has not yet indexed.

Diversity (Observer Protocol agents)

diversity = min(100, round(log(uniqueCounterparties + 1) / log(51) * 100))

Additional Bonuses

Verified Transaction Bonus (max +15)

Agents with verified Observer Protocol transactions receive +0.5 points per verified tx, capped at +15. This applies to all agent types, including Lightning nodes that also have Observer Protocol activity.

Popularity Bonus (max +10)

Each scored query is a demand signal. The bonus is min(10, round(log2(queryCount + 1) * 2)). An agent queried 100 times receives +13 (capped at +10).

LN+ Community Ratings Bonus (max +8)

Positive ratings from LN+ contribute a bonus: min(8, round(log2(positive + 1) * ratingsRatio * 3)) where ratingsRatio = positive / (positive + negative + 1). This keeps the social signal as a reward, not a requirement.

Confidence Levels

Confidence indicates how much data backs the score. It is based on total data points (transactions, attestations, channels, and LN+ ratings):

LevelData PointsInterpretation
very_low< 5Insufficient data. Score is unreliable.
low5 – 19Limited data. Use with caution.
medium20 – 99Moderate evidence. Score is indicative.
high100 – 499Substantial evidence. Score is reliable.
very_high≥ 500Extensive evidence. High trust in score accuracy.

Data Sources

Observer Protocol

Primary source for agent-to-agent transactions. Provides transaction records with payment hashes, preimages, status (verified/pending/failed), and protocol type (L402, keysend, bolt11). Transactions are the basis for volume, regularity, and diversity computation for non-Lightning agents.

LND Graph (Primary)

Full Lightning Network graph from our own LND node, backed by a full Bitcoin Core (bitcoind v28.1) node for channel validation. Currently indexing 13,913 active Lightning nodes — cryptographically verified against the Bitcoin UTXO set — with 4,225 stale nodes (not seen in 90+ days) preserved for history but excluded from scoring. Coverage grows automatically through auto-indexation: every query for an unknown node triggers background indexing. The more agents use SatRank, the more complete the graph becomes. All numbers here are mirrored from the live /api/stats endpoint — if this paragraph ever disagrees with the landing page, the landing page wins.

mempool.space (Fallback)

Lightning Network graph data via public API. Used as fallback when the LND node is unavailable or not synced. Limited to top 100 nodes by connectivity. Verifiable at https://mempool.space/lightning/node/{publicKey}.

LightningNetwork.Plus (LN+)

Community reputation platform. Provides positive/negative ratings and graph centrality metrics (hubness, betweenness, hopness). Centrality data feeds the reputation component; user ratings contribute a separate bonus (max +8 pts). Verifiable at https://lightningnetwork.plus/nodes/{publicKey}.

Route Probes

Periodic reachability checks via LND’s QueryRoutes API. Each indexed node is probed every 30 minutes to determine whether a route exists from SatRank’s node. Hot nodes — those recently queried via /decide or /ping within the last 2 hours — are probed first in each cycle so live traffic gets the freshest data. The unreachable flag in the verdict comes from probe data. Uptime ratios over 7 days are computed and exposed in the agent profile.

Data Validation

All channel data is validated against the Bitcoin UTXO set via a full bitcoind node (v28.1). This means every channel’s capacity is cryptographically verified — not estimated from gossip alone. Nodes running lightweight backends (Neutrino, SPV) cannot perform this validation, which is why many Lightning explorers report inflated node counts and phantom channels.

SatRank’s full-node validation reduced the apparent graph by 4,225 nodes to 13,913 verified active nodes. The excluded nodes are stale (not seen in 90+ days) and are preserved in the database but excluded from scoring, publishing, and statistics. If a stale node reappears in the gossip with a recent update, it is automatically restored to the active set — no history is lost.

This is the reason SatRank’s numbers will diverge from other Lightning dashboards that rely on Neutrino or SPV. Both pictures are internally consistent; only one is validated end-to-end against Bitcoin.

Anti-Gaming Mechanisms

SatRank employs multiple layers of defense against score manipulation. The mechanisms are described conceptually below; exact thresholds are proprietary to prevent circumvention.

Limitations & Centralization Risk

Our scores correlate with node size. Volume, capacity, seniority, and peer trust all favor large, established nodes. Using the total score as the sole routing criterion will concentrate traffic on dominant nodes.

We recommend agents use individual components and evidence to make nuanced decisions. A small node with high reputation and low volume may be a better choice for network resilience than a large node with a high total score. The /agents/top?sort_by=reputation endpoint lets you discover these nodes directly.

The total score measures individual reliability, not value to the network. These are different things.

As the attestation system matures, peer-to-peer trust signals will increasingly complement infrastructure metrics, enabling trust to emerge from consensus rather than size.

Evidence and Verification

Every score response includes an evidence object containing verifiable source data:

The evidence structure enables any consumer to cross-check the score against primary sources without trusting SatRank exclusively.

Worked Example: ACINQ

ACINQ is a major Lightning node operator with ~2,004 channels and hundreds of BTC of verified capacity. Here is how the score is computed with full-node validation:

ComponentInputScoreWeighted
Volume 2,004 channels (log scale, ref 500) 100 100 × 0.25 = 25.0
Reputation Hubness + betweenness top-10, peer trust from validated capacity ~77 77 × 0.30 = 23.1
Seniority ~2,921 days (8 years, half-life 730d) ~98 98 × 0.15 = 14.7
Regularity Reachable every probe, ~5% latency jitter, stable 1-hop routes ~85 85 × 0.15 = 12.75
Diversity ~1,800 unique peers (validated against the LND graph) 100 100 × 0.15 = 15.0
Base score ~91
+ Popularity bonus (log2-scaled queries) +2
Final score ~93*

* Values updated April 2026 after the v15 scoring calibration. Under pre-v15 scoring, ACINQ had regularity=100 (uptime only) and received two probe bonuses (+3 low-latency, +2 short-hop) that duplicated the regularity signal, landing at 100. The new multi-axis regularity reflects natural latency variance and route stability, and the probe bonuses are removed; the resulting 93 is the honest measure of ACINQ’s reliability. Actual scores are computed in real-time and may differ slightly. ACINQ has 0 LN+ ratings, so no LN+ bonus is applied.

Verdict API — SAFE / RISKY / UNKNOWN

An AI agent evaluating a 10,000 sat transaction has less than 200ms to decide. A composite score from 0 to 100 requires interpretation — what threshold is safe? How should a score of 47 be treated differently from 52? These are questions an autonomous agent should not have to answer in real-time.

The Verdict API (GET /api/agent/{hash}/verdict) returns a single, actionable decision: SAFE, RISKY, or UNKNOWN. No interpretation required.

Decision Logic

VerdictCondition
RISKYScore < 30 with confidence ≥ low (0.25), OR delta_7d < −15, OR negative_reputation flag, OR fraud_reported flag
SAFEScore ≥ 47, no critical flags, confidence ≥ medium (0.5)
UNKNOWNAgent not found, OR score 30–46, OR score < 30 with very low confidence, OR score ≥ 47 with confidence < medium

Why 47, not 50? Until v15 the SAFE threshold was 50 on a 0–100 scale that practically saturated at 100 (seven nodes tied at the cap). The v15 calibration replaced two proxies with real signals: diversity now measures unique peers from the validated LND graph instead of BTC capacity, and regularity now measures multi-axis consistency (uptime, latency, route stability) instead of uptime alone. This corrected roughly 1,000 nodes whose previous scores were inflated by the old proxies — nodes with concentrated BTC on a handful of peers, for instance, saw their diversity drop from 100 to about 18. The best nodes realistically score 93–94 post-calibration, so the SAFE threshold was moved from 50 to 47 to preserve the exact semantic: “SAFE = top 50% of the theoretical maximum.” 47/94 ≈ 50/100. The meaning of SAFE is unchanged; the number follows the new scale.

UNKNOWN is the honest answer when data is insufficient to commit either way. A score of 42 with low confidence could be a legitimate new node or a sophisticated fake — the system signals uncertainty rather than forcing a premature judgment. Agents receiving UNKNOWN should retry after the node accumulates more history, or use caller_pubkey for a personal trust graph assessment.

Confidence Mapping

Confidence is a numeric value (0–1) derived from the existing confidence level: very_low=0.1, low=0.25, medium=0.5, high=0.75, very_high=0.9. UNKNOWN agents return confidence 0.

Flags

Flags provide machine-readable context for the verdict:

Structured Attestation Categories

When submitting attestations via POST /api/attestation, agents can now include a category field: successful_transaction, failed_transaction, dispute, fraud, unresponsive, or general (default).

Attestations with category fraud or dispute automatically trigger the corresponding flag in the verdict, ensuring the agent is classified as RISKY regardless of their score. This is the foundation for a decentralized dispute resolution layer.

Attestations are free. They are the fuel of the trust network. Submitting attestations requires an API key for identity verification, but no Lightning payment (no L402). The more agents attest, the more accurate the scoring becomes for everyone.

Auto-Indexation

When you query /agent/{pubkey} or /verdict for an unknown Lightning pubkey (66 hex characters starting with 02 or 03), SatRank returns 202 Accepted and begins indexing that node in the background via our LND graph connection. Retry after 10 seconds to get the score.

This means the index grows organically as agents query new counterparties — usage drives coverage. The system is rate-limited (configurable, default 10 auto-indexes per minute) to prevent abuse, and deduplicates concurrent requests for the same pubkey.

Batch verdicts (POST /api/verdicts) also trigger auto-indexation for unknown Lightning pubkeys, capped at 2 per batch to prevent a single request from consuming the entire rate budget.

Batch Verdict

POST /api/verdicts accepts up to 100 hashes in a single request, returning an array of verdicts. This is 10x more efficient than individual queries for agents that need to screen multiple counterparties before transacting. Unknown Lightning pubkeys trigger auto-indexation automatically.

Personal Trust Graph

A global score tells you what the network thinks. A personal trust graph tells you what your network thinks. This is the difference between “score 72” and “3 agents you trust have positively attested this node.”

When calling GET /api/agent/{hash}/verdict, pass your own pubkey hash via the caller_pubkey query parameter or X-Caller-Pubkey header. SatRank traverses the attestation graph to compute a trust distance:

DistanceMeaningHow it’s computed
0 Direct trust You have positively attested this agent (score ≥ 70)
1 Friend-of-friend An agent you positively attested has also positively attested the target
2 Two degrees An agent attested by someone you trust has attested the target
null No connection No path found within 2 hops in the attestation graph

The response also includes sharedConnections (number of mutual trusted agents) and strongestConnection (alias of the highest-scoring shared agent).

The trust graph builds naturally as agents submit attestations after transactions. No registration or explicit “follow” mechanism is needed — the attestation history is the social graph.

If caller_pubkey is not provided, personalTrust is null and the verdict uses only the global score. There is no regression for existing consumers.

Risk Profiles

A score of 55 for a 3-year-old hub means something very different than 55 for a 10-day-old node. Risk profiles classify agents into behavioral categories based on observable properties, giving the verdict context that a single number cannot convey.

ProfileRiskCriteria
established_hub Low Score ≥ 70, seniority > 365 days, > 200 transactions
small_reliable Low Score 40–69, seniority > 365 days, regularity > 60
growing_node Medium delta_7d > 10, seniority < 180 days
new_unproven High Seniority < 30 days, total transactions < 5
declining_node High delta_7d < −10, trend = falling
suspicious_rapid_rise High delta_7d > 20, seniority < 60 days
default Unknown Does not match any specific profile

Profiles are evaluated in priority order (suspicious patterns first, then established trust, then growth). The first match wins. The profile is a complement to the verdict, not a replacement — an established_hub classified as RISKY due to a fraud attestation is more alarming than a new_unproven classified as RISKY for having no history.

Each profile includes a human-readable description with the specific values that triggered the classification (e.g., “Active for 45 days, rapid score increase (+18 in 7d). Monitor closely.”).

Score History & Deltas

A score is a snapshot. A delta is a trajectory. SatRank stores score snapshots over time and computes deltas at three intervals:

FieldWindowUse Case
delta24h24 hoursDetect sudden drops or spikes after an incident
delta7d7 daysPrimary trend signal. Used by verdict logic and risk profiles
delta30d30 daysLong-term trajectory. Identifies sustained improvement or degradation

All deltas are null when no historical snapshot exists for the given window. A delta of 0 means the score was measured at both endpoints and did not change.

Trend Direction

Derived from delta7d:

if delta7d is null or between -2 and +2 → stable
if delta7d > 2                          → rising
if delta7d < -2                         → falling

The ±2 dead zone prevents noise from minor score fluctuations. Only meaningful directional changes register as rising or falling.

History Endpoint

GET /api/agent/{hash}/history returns paginated score snapshots with per-entry deltas (difference from previous snapshot), plus an aggregate delta summary for the agent:

{
  "data": [
    { "score": 72, "components": {...}, "computedAt": "2026-04-01T...", "delta": +3 },
    { "score": 69, "components": {...}, "computedAt": "2026-03-31T...", "delta": -1 },
    ...
  ],
  "delta": { "delta24h": 3, "delta7d": 8, "delta30d": 12, "trend": "rising" },
  "meta": { "total": 45, "limit": 20, "offset": 0 }
}

Top Movers

GET /api/agents/movers returns the 5 agents with the largest score increases and the 5 with the largest decreases over the past 7 days. This surfaces emerging trust signals and developing problems before they reach the individual agent level.

Alerts

The score response (GET /api/agent/{hash}) includes an alerts array with machine-readable signals about the agent's current state. Alerts are computed from deltas, age, and activity:

Alert TypeSeverityTrigger
score_drop warning or critical delta_7d ≤ −10 (warning) or ≤ −20 (critical)
score_surge info delta_7d ≥ +15
new_agent info First seen ≤ 7 days ago
inactive info Last seen ≥ 60 days ago

Alerts complement verdicts. A SAFE agent with a score_drop alert is still safe today but may not be next week. A score_surge on a new agent warrants the same scrutiny as the suspicious_rapid_rise risk profile.

Alerts differ from verdict flags: flags affect the verdict decision itself (fraud_reported forces RISKY), while alerts are informational and do not change the verdict outcome.

Personalized Pathfinding

Probe data tells you whether our node can reach a target. Personalized pathfinding tells you whether you can reach the target. This is the difference between “reachable from SatRank” and “reachable from your position in the graph.”

When calling GET /api/agent/{hash}/verdict with a caller_pubkey, SatRank performs a real-time route query from the caller’s position to the target using LND’s QueryRoutes API with the source_pub_key parameter. No payment is sent — this is a read-only graph operation.

How It Works

  1. The caller provides their pubkey hash (or 66-char Lightning pubkey) via caller_pubkey
  2. SatRank looks up both the caller’s and target’s Lightning public keys in the agents table
  3. If both are indexed Lightning nodes, SatRank calls GET /v1/graph/routes/{target}/{amt}?source_pub_key={caller} on our LND node
  4. LND computes Dijkstra pathfinding on the public Lightning graph from the caller’s topological position
  5. The result is returned as a pathfinding field in the verdict response

Response Fields

FieldTypeDescription
reachablebooleanWhether a route exists from the caller to the target
hopsinteger | nullNumber of hops in the best route
estimatedFeeMsatinteger | nullEstimated routing fee in millisatoshis
alternativesintegerNumber of alternative routes found
latencyMsintegerRoute computation time in milliseconds
sourcestringAlways lnd_queryroutes

When Pathfinding Is Unavailable

pathfinding is null when: caller_pubkey is not provided, the caller is not an indexed Lightning node, the target has no Lightning pubkey, or the LND node is unreachable. The verdict still returns normally using cached probe data and the global score — pathfinding is additive, never blocking.

The unreachable_from_caller Flag

When pathfinding finds no route from the caller to the target, the flag unreachable_from_caller is added to the verdict. This is distinct from the unreachable flag (no route from our node). A target can be reachable from SatRank but not from the caller, or vice versa.

Caching

Results are cached for 5 minutes per (caller, target) pair. The Lightning graph topology changes slowly enough that a 5-minute TTL provides accurate results while keeping LND query volume manageable.

Competitive Advantage

No free Lightning service provides personalized pathfinding as an API. mempool.space shows the graph. Amboss shows node statistics. 1ML shows connectivity. None of them answer the question: “Can I pay this node, and what will it cost me?”

Decision Engine

Scores answer “how trustworthy is this agent?” The decision engine answers “should I transact with this agent right now?” It combines the trust score with real-time signals — route availability, probe uptime, and historical outcomes — into a single GO / NO-GO decision.

EndpointAuthDescription
POST /api/decide1 satGO / NO-GO with success probability
POST /api/reportAPI KeyReport transaction outcome (free)
GET /api/profile/{id}1 satAgent profile with reports, uptime, rank

Decide

The decide endpoint returns a boolean go (true/false) and a successRate (0–1) composed from 4 probability signals:

P_trust     = sigmoid(score, midpoint=50, steepness=0.1)   // SatRank score → probability
P_routable  = 1.0 if pathfinding finds a route, else 0.0    // Can I reach this node?
P_available = probe uptime over 7 days (0-1)                 // Is this node online?
P_empirical = weighted success rate from past reports         // Did other agents succeed?

When fewer than 10 reports from 5+ unique reporters exist (proxy mode):

successRate = P_trust * 0.4 + P_routable * 0.3 + P_available * 0.3

When 10+ reports from 5+ unique reporters exist (empirical mode):

successRate = P_empirical * 0.45 + P_trust * 0.10 + P_routable * 0.225 + P_available * 0.225

P_trust is kept at 10% even in empirical mode as a safety net. Reports measure past success, but a node whose infrastructure is degrading (channels closing, probe instability) should still trigger a warning even if its last 15 reports were positive.

The threshold requires both sufficient data and diverse reporters — 10 reports from a single agent do not trigger empirical mode. This prevents self-reporting manipulation.

go = true when successRate ≥ 0.5 and no critical flags (fraud_reported, negative_reputation).

Report

After a transaction, agents report the outcome: success, failure, or timeout. Reports are free (no payment required) to maximize participation.

Feedback Loop

The decide → pay → report cycle creates a closed feedback loop. The more agents report outcomes, the more accurate P_empirical becomes, and the better future decisions perform. This is SatRank’s flywheel: usage generates data, data improves decisions, better decisions attract more usage.

Predictive Signals

Scores describe the present. Predictive signals answer: will this node still be here in 7 days? All signals below are derived from data already in the index — no external dependencies, no machine learning.

Survival Score (0–100)

Predicts whether a node will remain reachable, based on three weighted signals:

SignalWeightSourceThresholds
Score Trajectory40%Score snapshots (7-day slope) Slope < −2/day: −40. Slope −1 to −2: −20. Stable/positive: 0.
Probe Stability40%Probe results (uptime ratio) 0% uptime: −40. < 50%: −30. < 80%: −15. ≥ 80%: 0.
Gossip Freshness20%Last gossip timestamp > 14 days: −20 (zombie). > 7 days: −10 (stale). ≤ 7 days: 0.
survival_score = clamp(0, 100, 100 + sum(adjustments))
prediction = score > 70 ? "stable" : score > 40 ? "at_risk" : "likely_dead"

Channel Flow

Net change in channel count and capacity over 7 days. A node losing 3+ channels/week is declining. A node gaining 3+ is growing. Derived from hourly channel snapshots stored during each LND graph crawl.

Capacity Drain Rate

Percentage change in total capacity over 24 hours and 7 days. A drain rate ≤ −30% triggers the capacity_drain flag. ≤ −50% triggers severe_capacity_drain. A node hemorrhaging capacity is likely closing channels or being force-closed by peers.

Fee Volatility Index (FVI)

Measures how often a node changes its fee policies. Computed as the average number of fee changes per channel per day, normalized to 0–100. Interpretation: < 10 = stable, < 30 = moderate, ≥ 30 = volatile. A node that spikes fees from 1 ppm to 1000 ppm overnight is volatile and may indicate distress or adversarial behavior.

Gossip Freshness Flags

Two flags based on the last gossip update timestamp:

L402 Payment as Data

Accessing scored endpoints requires an L402 micropayment of 1 satoshi. This is not just an access control mechanism — it is a data point.

Each paid query is an anonymous, permissionless signal of demand. An agent that is queried frequently is one that others care about — partners evaluating transactions, services checking trust, or researchers monitoring the network. This demand signal is captured in the popularity bonus (max +10 points).

Unlike page views or API hits, L402 queries have a nonzero cost, which filters out noise. A bot scraping every agent generates signal at 1 sat per query — sustainable for legitimate research, expensive for spam.

Nostr Distribution (NIP-85)

SatRank publishes trust scores as Nostr Trusted Assertions (NIP-85, kind 30382). Every 6 hours, scores for all active agents above threshold (score ≥ 30) are published to three public relays:

Each event carries the NIP-85 canonical rank tag (normalized 0-100 score) alongside SatRank-specific tags: the 5 scoring components (volume, reputation, seniority, regularity, diversity), verdict (SAFE / RISKY / UNKNOWN), reachability, and a survival prediction. Strict NIP-85 consumers read rank without needing SatRank-specific knowledge; clients that want the richer signal read the component tags too. Events are replaceable (same d-tag per agent pubkey), so clients always see the latest score with no duplication.

Scope note. NIP-85’s “User as Subject” is defined for a 32-byte pubkey. SatRank extends the semantics to Lightning node pubkeys — same secp256k1 format, different key space. We’re the first provider to bridge the Lightning payment graph into the NIP-85 ecosystem; every other implementation operates on the Nostr social graph.

Dual publishing — extension + strict conformance

Because NIP-85 as written targets 32-byte Nostr pubkeys and SatRank’s primary signal targets 66-byte Lightning pubkeys, we publish kind 30382 events in two indexed namespaces so clients can read either one:

How we build the mapping. NIP-57 zap receipts (kind 9735) contain the recipient’s Nostr pubkey in the p tag and the paid invoice in the bolt11 tag. The BOLT11 invoice’s destination (payee_node_key) is cryptographically embedded in the invoice signature — we decode it and cross-reference with the p tag to get a verified (nostr_pubkey, ln_pubkey) tuple. The miner paginates backwards across 6 relays (damus.io, nos.lol, relay.primal.net, relay.nostr.band, nostr.wine, relay.snort.social) using NIP-01 until-based pagination, walking up to 40 pages of 500 events per relay with a 60-day age wall. The first paginated run processed ~20,000 distinct receipts (100 % BOLT11-decodable), resolved 302 distinct Lightning pubkeys, and produced 281 self-hosted candidates — from which 65 survive the production filter below.

Custodian filter. Any ln_pubkey paired with more than 1 distinct Nostr pubkey in the mining sample is rejected as likely custodial or shared (multiple users on the same wallet provider), and any node whose alias matches known custodial / LSP patterns (zlnd*, lndus*, *coordinator*, *.cash, zeus, alby, wos, cashu, minibits, phoenix, breez, muun, primal, nwc, fountain, wavlake, fedi, fewsats, lightspark, voltage, strike, …) is also rejected. What remains is a conservative set of self-hosted operators, each paired with exactly the Nostr identity that zaps them. An optional relaxed mode (ALLOW_SHARED_LNPK=1) publishes up to 5 nostr pubkeys per ln_pubkey for shared family/team nodes; the default is strict = 1.

Stream B events carry the same rank / verdict / component tags as Stream A, plus ln_pubkey (for traceability back to the Lightning node), subject_type (mined_mapping or self_declaration), source (nip57_zap_receipt), and zap_count (evidence strength). Clients already consuming Stream A need zero extra code to parse Stream B — they’re identical except for the d tag.

The mining and publishing logic live in scripts/nostr-mine-zap-mappings.ts and scripts/nostr-publish-nostr-indexed.ts; the one-shot self-declaration (SatRank’s own Nostr pubkey → SatRank’s own Lightning node) is in scripts/nostr-publish-self-declaration.ts.

Why Nostr?

Nostr is the natural distribution layer for trust data in the Bitcoin ecosystem:

DVM Trust-Check (NIP-90)

SatRank also operates a Data Vending Machine (NIP-90) for real-time trust queries. Publish a kind 5900 job request with ["j", "trust-check"] and ["i", "<ln_pubkey>", "text"] tags, and SatRank responds with a signed kind 6900 event containing the trust score, verdict, and reachability. Unknown nodes trigger an on-demand QueryRoutes probe so the answer reflects the live graph, not just the cached snapshot. The DVM is discoverable via a kind 31990 handler-info event published to the three canonical relays on every startup. Free, no payment required.

Unique Position

Every other NIP-85 implementation (Brainstorm, wot-scoring, nostr-wot-sdk, nostr-wot-oracle, Vertex) operates on the Nostr social graph — scoring based on follows, mutes, and zaps. SatRank is the only provider that bridges the Lightning payment graph into NIP-85. Because Brainstorm and SatRank both use the canonical rank tag on kind 30382 events, a wallet or agent can list both in its kind 10040 and receive social trust and payment reliability from the same Nostr relay connection — verified live on relay.damus.io and nos.lol, where both providers publish.

Declaring SatRank as a Trusted Provider (kind 10040)

NIP-85 kind 10040 is the user-side declaration that lists your trusted providers for each <kind:tag> pair. To add SatRank to your own trusted provider list, publish a kind 10040 event with one tag per (provider, relay) combo:

{
  "kind": 10040,
  "tags": [
    ["30382:rank", "5d11d46de1ba4d3295a33658df12eebb5384d6d6679f05b65fec3c86707de7d4", "wss://relay.damus.io"],
    ["30382:rank", "5d11d46de1ba4d3295a33658df12eebb5384d6d6679f05b65fec3c86707de7d4", "wss://nos.lol"],
    ["30382:rank", "5d11d46de1ba4d3295a33658df12eebb5384d6d6679f05b65fec3c86707de7d4", "wss://relay.primal.net"]
  ],
  "content": ""
}

Multiple relay entries are independent — a client picks whichever relay is reachable for the user. You can combine SatRank with other NIP-85 providers in the same kind 10040 event: each provider gets its own row, and clients query all of them in parallel.

SatRank itself publishes a self-declaration kind 10040 from its service key so clients have an on-chain reference example to copy. Query it with:

["REQ", "satrank-10040", {
  "kinds": [10040],
  "authors": ["5d11d46de1ba4d3295a33658df12eebb5384d6d6679f05b65fec3c86707de7d4"]
}]

Live circuit — kind 0 / 30382 / 10040

This page is not just documentation — it is an end-to-end probe of the SatRank Nostr circuit. On every visit, your browser opens a WebSocket to each of the three canonical relays (relay.damus.io, nos.lol, relay.primal.net) and issues a REQ for kind 0 (profile), kind 30382 (trusted assertions) and kind 10040 (trusted provider declaration) authored by SatRank’s service pubkey. The results below are the real events currently on the network — if the circuit is broken, this block tells you directly. If the self-declaration has not yet been published, the kind 10040 count will honestly read 0 until the script in scripts/nostr-publish-10040.ts is run.

connecting...
kind 0
profile metadata
? events
kind 30382
trusted assertions · dual-indexed (Stream A + B)
? events
kind 10040
trusted provider declaration
? events

NIP-05: satrank@satrank.dev
Pubkey: 5d11d46de1ba4d3295a33658df12eebb5384d6d6679f05b65fec3c86707de7d4