PR #607 added a second hardcoded default relay (wss://mostro-p2p.tech). These hardcoded relays are loaded as RelaySource.defaultConfig: persisted, always present in the user's relay list, always connected, and surviving sync unless blacklisted. This causes idle connections to relays the active Mostro may not use, divergence between the visible list and Mostro's endorsed relays, and UX special-casing in relay_selector.dart.
This issue replaces that approach with a defensive, internal-only bootstrap mechanism.
What will be done
Introduce a hidden, internal list of bootstrap relays that the app uses behind the scenes, only when needed, with the sole purpose of fetching the Mostro instance's kind 10002 relay list. Once the real relays are discovered from the 10002, those become the relays loaded in the app and shown to the user, and all subsequent queries go exclusively through them, not through the bootstrap relays.
When bootstrap is activated
Bootstrap must not run on every launch. The relays discovered from the last kind 10002 are already persisted (settings.relays, RelaySource.mostro), so a normal restart simply loads and uses them. The central condition is: do we currently have at least one discovered relay that connects? If yes, bootstrap never runs.
On startup:
- Load the persisted discovered relays (from the last 10002).
- Attempt to connect to them.
- If ≥1 connects within a 5–8s timeout → normal operation, bootstrap is not used at all.
- If 0 connect (all unreachable) within the 5–8s timeout → activate bootstrap to re-fetch the 10002.
First launch ever (no persisted relays): activate bootstrap immediately, since there is nothing to load.
During an active session: if all discovered relays drop and reconnection fails, re-activate bootstrap automatically to recover connectivity and re-fetch the 10002. Bootstrap is evaluated both at startup and at runtime, but only as a total-failure fallback.
Normal 10002 updates are unaffected: when Mostro updates its relay list, the new 10002 arrives in real time through the already-connected discovered relays (existing real-time sync). Bootstrap plays no part in normal updates, it is strictly the cold-start / total-failure path.
Discovery and retirement
- Discovery phase: When activated, the app connects to the bootstrap relays solely to fetch Mostro's kind 10002 event.
- Load discovered relays: The relays found in the 10002 are loaded into the app and are the only ones shown to the user.
- Operate through discovered relays: After discovery, all event fetching/publishing goes through the discovered relays, not the bootstrap relays.
- Retirement: Bootstrap connections are dropped once a valid kind 10002 has been received and at least one discovered relay is confirmed reachable.
- Mostro offline / no 10002: If the 10002 never arrives and no discovered relay is alive, the app keeps retrying the bootstrap relays with a timeout/backoff, staying active until something responds, so the app is never left without a path to discover relays.
Constraints
- Bootstrap relays are fully hidden: never shown in the UI or settings, never persisted into the user's relay list. They are pure internal infrastructure.
- Bootstrap connections live outside RelaysNotifier state, so they never trigger the promotion/elimination sync logic and never appear among user/Mostro relays.
- The bootstrap list is a dedicated internal constant, separate from the relays persisted for a given instance.
Cleanup enabled by this change:
- Removes the always-present idle defaultConfig connections introduced as defaults.
- Removes the wss://relay.mostro.network special-casing in relay_selector.dart (~line 250), since bootstrap relays never reach the UI.
Scope: Architectural change touching NostrService + RelaysNotifier. Intentionally separate from PR #607, which ships the hardcoded defaults as an interim mitigation.
PR #607 added a second hardcoded default relay (wss://mostro-p2p.tech). These hardcoded relays are loaded as RelaySource.defaultConfig: persisted, always present in the user's relay list, always connected, and surviving sync unless blacklisted. This causes idle connections to relays the active Mostro may not use, divergence between the visible list and Mostro's endorsed relays, and UX special-casing in relay_selector.dart.
This issue replaces that approach with a defensive, internal-only bootstrap mechanism.
What will be done
Introduce a hidden, internal list of bootstrap relays that the app uses behind the scenes, only when needed, with the sole purpose of fetching the Mostro instance's kind 10002 relay list. Once the real relays are discovered from the 10002, those become the relays loaded in the app and shown to the user, and all subsequent queries go exclusively through them, not through the bootstrap relays.
When bootstrap is activated
Bootstrap must not run on every launch. The relays discovered from the last kind 10002 are already persisted (settings.relays, RelaySource.mostro), so a normal restart simply loads and uses them. The central condition is: do we currently have at least one discovered relay that connects? If yes, bootstrap never runs.
On startup:
First launch ever (no persisted relays): activate bootstrap immediately, since there is nothing to load.
During an active session: if all discovered relays drop and reconnection fails, re-activate bootstrap automatically to recover connectivity and re-fetch the 10002. Bootstrap is evaluated both at startup and at runtime, but only as a total-failure fallback.
Normal 10002 updates are unaffected: when Mostro updates its relay list, the new 10002 arrives in real time through the already-connected discovered relays (existing real-time sync). Bootstrap plays no part in normal updates, it is strictly the cold-start / total-failure path.
Discovery and retirement
Constraints
Cleanup enabled by this change:
Scope: Architectural change touching NostrService + RelaysNotifier. Intentionally separate from PR #607, which ships the hardcoded defaults as an interim mitigation.