The GrapeVine API computes Web of Trust influence scores using an iterative convergence algorithm over the follow graph. It uses Badger's materialized graph indexes (ppg/gpp) for fast traversal, avoiding the need for Neo4j or SQL databases.
The API is disabled by default. Enable it with:
ORLY_GRAPEVINE_ENABLED=true
This requires the Badger database backend (ORLY_DB_TYPE=badger, which is the default).
| Variable | Default | Description |
|---|---|---|
ORLY_GRAPEVINE_ENABLED | false | Enable the GrapeVine scoring API |
ORLY_GRAPEVINE_MAX_DEPTH | 6 | Maximum BFS depth for follow graph traversal |
ORLY_GRAPEVINE_CYCLES | 5 | Number of convergence iterations |
ORLY_GRAPEVINE_ATTENUATION | 0.8 | Weight decay factor per hop (0-1). Lower = faster decay. |
ORLY_GRAPEVINE_RIGOR | 0.25 | Certainty curve steepness (0-1). Lower = more certainty from fewer inputs. |
ORLY_GRAPEVINE_FOLLOW_CONFIDENCE | 0.05 | Base confidence weight for a follow edge |
ORLY_GRAPEVINE_OBSERVERS | Comma-separated hex pubkeys to auto-calculate scores for | |
ORLY_GRAPEVINE_REFRESH | 6h | How often to recalculate scores for configured observers |
ORLY_GRAPEVINE_ENABLED=true
ORLY_GRAPEVINE_OBSERVERS=abc123...hex64,def456...hex64
ORLY_GRAPEVINE_REFRESH=4h
ORLY_GRAPEVINE_MAX_DEPTH=4
The scoring algorithm has two components:
MAX_DEPTH hopsCYCLES iterations: - For each pubkey (ratee) in the set:
- Find all followers of that ratee who are also in the set
- Each follower's "vote" is weighted by: ATTENUATION * follower_influence * FOLLOW_CONFIDENCE
- The observer's own follows get no attenuation (direct trust)
- average = sum_of_products / sum_of_weights
- certainty = 1 - exp(-sum_of_weights * -ln(RIGOR))
- influence = average * certainty
For each pubkey in the hop set, count how many of the observer's direct follows also follow that pubkey. This gives a simple "social proof" metric independent of the influence calculation.
Mute lists (kind 10000) and report events (kind 1984) are not yet factored into the calculation. The algorithm has TODO hooks for integrating these signals.
All endpoints require NIP-98 HTTP authentication. Use the nurl tool for testing:
# Build nurl
go build -o nurl ./cmd/nurl
Returns all scores for an observer.
Parameters:
observer (query, optional): Hex pubkey of the observer. Defaults to the authenticated pubkey.Authorization: Any authenticated user can query their own scores. Owner/admin can query any observer.
Example:
NOSTR_SECRET_KEY=nsec1... ./nurl "https://relay.example.com/api/grapevine/scores?observer=abc123..."
Response (200):
{
"observer": "abc123...",
"scores": [
{
"pubkey": "def456...",
"influence": 0.85,
"average": 0.90,
"certainty": 0.94,
"input": 0.15,
"wot_score": 42,
"depth": 1
}
],
"computed_at": "2026-03-12T10:30:00Z",
"compute_ms": 4500,
"total_pubkeys": 1234
}
Response (404): No scores computed yet for this observer.
Returns a single score for an observer+target pair.
Parameters:
observer (query, optional): Hex pubkey of the observer. Defaults to authenticated pubkey.target (query, required): Hex pubkey of the target.Example:
NOSTR_SECRET_KEY=nsec1... ./nurl "https://relay.example.com/api/grapevine/score?observer=abc123...&target=def456..."
Response (200):
{
"observer": "abc123...",
"target": "def456...",
"influence": 0.85,
"average": 0.90,
"certainty": 0.94,
"input": 0.15,
"wot_score": 42,
"depth": 1
}
Triggers an async score recalculation for an observer. Returns immediately.
Body (JSON):
{"observer": "abc123..."}
If observer is omitted, uses the authenticated pubkey. Owner/admin can trigger for any observer; others can only trigger their own.
Example:
NOSTR_SECRET_KEY=nsec1... ./nurl "https://relay.example.com/api/grapevine/recalculate" '{"observer":"abc123..."}'
Response (202):
{"status": "started", "observer": "abc123..."}
Or if already computing:
{"status": "already_computing", "observer": "abc123..."}
| Field | Type | Description |
|---|---|---|
pubkey | string | Hex pubkey of the scored user |
influence | float64 | Combined score: average * certainty (0.0 to 1.0) |
average | float64 | Weighted average of rater scores (0.0 to 1.0) |
certainty | float64 | Confidence based on total input weight (0.0 to 1.0) |
input | float64 | Raw sum of input weights from raters |
wot_score | int | Number of observer's follows who also follow this pubkey |
depth | int | BFS hop distance from observer (1 = direct follow) |
Scores are persisted in Badger under the key prefixes GV_SET_ (full score sets) and GV_ENT_ (individual entries). They survive relay restarts. A recalculation replaces the previous scores for that observer.
The algorithm uses Badger's materialized ppg/gpp indexes, which encode follow graph edges as 21-byte binary keys. Each follower lookup is a single sorted prefix scan — no event decoding, no SQL parsing.
For a typical relay with ~50k pubkeys in the 2-hop set and 5 convergence cycles, computation completes in seconds. Follower lookups are cached within a single computation run.