docs: explain geohash relay selection for external publishers

Document proximity-based relay picking, sparse-cell limits, and
ephemeral delivery so tools publishing kind 20000/20001 can reach
BitChat clients.
This commit is contained in:
Taksh 2026-07-27 10:05:01 +03:00
parent 1f59e814f9
commit 8fddb49146
3 changed files with 90 additions and 0 deletions

View File

@ -81,6 +81,7 @@ construction, not NIP-44 encryption.
- `region` (2 chars): Country/large region
- **Internet**: Required (connects to Nostr relays)
- **Use Case**: Location-based community chat, local events, regional discussions
- **External publishers:** relays are chosen by proximity to the geohash — see [Geohash publisher relays](docs/GEOHASH-PUBLISHER-RELAYS.md)
### Direct Message Routing

View File

@ -0,0 +1,83 @@
# Geohash Publisher Relay Selection & Delivery
Guide for external tools that publish into BitChat location channels (kind
`20000` chat, kind `20001` presence). BitChat clients do **not** use a fixed
global relay set for these channels — they pick relays by proximity to the
geohash cell. A publisher that targets the wrong relays will never reach those
clients.
## Event shape
Location chat messages are ephemeral Nostr events:
| Field | Value |
|-------|--------|
| `kind` | `20000` (chat) or `20001` (presence heartbeat) |
| `tags` | Must include `["g", "<geohash>"]`. Chat may also carry `["n", "<nickname>"]`. |
| `content` | Message text for kind `20000`; empty for kind `20001` |
| `pubkey` | Ephemeral identity derived for that geohash (not the user's long-term key) |
Clients subscribe with filters equivalent to:
```json
{ "kinds": [20000, 20001], "#g": ["<geohash>"], "limit": 200 }
```
See [GeohashPresenceSpec.md](GeohashPresenceSpec.md) for presence broadcast
rules (precision limits, heartbeat cadence).
## How clients choose relays
On subscribe and publish, BitChat asks `GeoRelayDirectory` for the closest
relays to the geohash center:
1. Decode the geohash to a lat/lon center.
2. Rank the bundled geo-relay directory by haversine distance.
3. Take the nearest **5** relays (`TransportConfig.nostrGeoRelayCount`).
4. Ties break by hostname so every device with the same directory picks the
same set — publishers and subscribers must agree.
With an empty or unreachable directory for that cell, the client refuses to
publish rather than falling back to unrelated default relays.
**Implication for external publishers:** replicate the same proximity selection
(or publish to a strict superset that includes those five hosts). Publishing
only to a personal fixed relay list is the most common reason a message never
appears in the app.
## Sparse and ocean cells
Some geohashes (oceans, deserts, polar regions) map to few or unreachable
nearby relays. The channel UI still opens normally — there is no separate
"undeliverable" banner — but live messages may not round-trip. Treat an empty
timeline in those cells as a relay-coverage problem, not a client bug.
## Ephemerality and late joiners
Kind `20000` / `20001` events are ephemeral. Relays typically do not store them
for historical replay. A client that subscribes minutes later cannot retrieve
messages that already left the relay's ephemeral buffer. Initial lookback on
subscribe is on the order of one hour for chat sampling
(`nostrGeohashInitialLookbackSeconds`), but that only recovers what the chosen
relays still hold.
For durable geographic notes, BitChat uses persistent kind `1` events tagged
with `g` (location notes / drops) — a different surface from live channel chat.
## Practical checklist for external publishers
1. Compute the target geohash at the precision the channel uses (`block` ≈ 7,
`neighborhood` ≈ 6, `city` ≈ 5, etc.).
2. Resolve the closest ~5 `wss://` hosts from the same geo-relay directory the
app ships (or an equivalent proximity ranking over that list).
3. Publish the signed kind-`20000` event to those relays with a correct `g` tag.
4. Expect no backlog for late subscribers; design tools around live delivery.
5. Do not assume App Store / mesh Bluetooth peers will see Nostr-only publishes
— mesh and geohash are separate transports.
## Related code
- `bitchat/Nostr/GeoRelayDirectory.swift` — proximity ranking
- `bitchat/Services/GeohashPresenceService.swift` — subscribe/publish wiring
- `bitchat/Services/TransportConfig.swift``nostrGeoRelayCount`, lookback limits
- `bitchat/Nostr/NostrProtocol.swift` — kind `20000` / `20001` builders

View File

@ -94,3 +94,9 @@ The presentation of the participant count depends on the geohash precision level
* **Privacy:** High-precision location presence is NOT broadcast. Temporal correlation between different levels is obfuscated via random delays.
* **Consistency:** "Online" status is maintained globally while the app is open.
* **Transparency:** The UI correctly reflects uncertainty (`?`) when privacy rules prevent accurate passive counting.
## External publishers
Tools that publish kind `20000` / `20001` into a geohash channel must target the
same proximity-selected relays BitChat clients use, or messages will not arrive.
See [GEOHASH-PUBLISHER-RELAYS.md](GEOHASH-PUBLISHER-RELAYS.md).