Hackathon
Relay Shadow
A Nostr data vending machine that recommends relays against a stated threat model, using relay and social-graph observations from BigBrotr's earlier PostgreSQL toolchain.
Relay Shadow answers “which relays should I use?”, as a service other software can call, over Nostr itself. It takes a threat level and a use case, scores candidate relays against measured data, and returns a ranked set.
- Result
- 3rd prize, Baltic Honeybadger 2025
- Interface
- NIP-90 DVM, kinds 5600 / 5601 / 7000
- Data
- PostgreSQL snapshot of BigBrotr relay observations
- Repository
- BigBrotr/relay-shadow-dvm
The problem
A client’s relay selection affects the events it can retrieve and the operators that observe its requests. A fixed default list cannot account for every user’s contacts, availability needs and privacy concerns.
There is measured data that could inform the choice. It sits in an archive, in a shape useful to an analyst rather than to a client at runtime.
What I built
A data vending machine: a service that receives jobs as Nostr events and replies with Nostr events, so clients supporting the job format can consume it over their existing relay connections.
Requests arrive as kind 5600, carrying their parameters in tags: threat level, use case, the
relay set the user currently has, how many results they want. Results are signed and published
as kind 5601, with kind 7000 used for job feedback. Four request types are supported:
recommend, analyze for evaluating a relay set already in use, discover for widening
coverage, and health.
This implementation reads a PostgreSQL copy of relay data from BigBrotr’s earlier toolchain, not the current Go archive. Scoring is implemented as query functions that combine four weighted factors: social graph, privacy, reliability, and performance. The default weighting places most of the emphasis on the social graph. There is a React demo client and a small Express endpoint exposing service info.
Hard parts
There is no best relay set, so the request has to say who is asking. Threat level is a
request parameter ranging from low to nation-state, and it reaches the scoring query rather
than being cosmetic. A relay that is excellent for reach can be exactly the wrong answer for
someone whose concern is who observes them, and a recommender returning one ranking for everybody
is answering a question nobody asked.
Recommending your own contacts back to you. Weighting the social graph most heavily is what makes a recommendation useful. A relay where nobody you follow publishes is not useful, but this weighting also means the service reads which relays a requester’s contacts use in order to answer. That access requirement belongs in the service’s privacy model.
Answering when the data cannot. The service falls back to a static list per threat level when the query returns nothing, so a request gets a degraded answer rather than an error. The scoring weights are design inputs rather than measured optima, so the response remains a recommendation, not a privacy guarantee.