Relay-strategy hardening (network-trust §3): don't hang on one relay.
- NostrChannel interface: what the transports need (sign, publish, subscribe,
reqOnce). Implemented by NostrConnection (single relay) and the new RelayPool.
- RelayPool: connects to all configured relays (skips unreachable ones, throws
only when none are reachable), publishes to every live relay (accepted if any
accepts), merges discovery deduped by event id, and survives a relay dropping.
- Transports now depend on NostrChannel, not a concrete connection.
- SocialService.openSession takes ALL configured relays (was relays.first);
createOffersCubit passes the whole list.
commons_core 63 tests green (3 new pool tests over two in-process relays);
app_seeds social/market 18 green.
Third slice: bridge Block 1's root seed to the Block 2 transport foundation.
- SocialService: derives the secp256k1 Nostr identity from the SAME root seed
the recovery QR backs up (no extra backup), exposes npub/pubkey, and opens a
SocialSession = one shared connection carrying all three transports.
- injector.dart: registers SocialService in get_it. Local-first — derivation is
cheap + offline; no relay is contacted at startup. Replaces the old
"nodeId slice of the seed is the social pubkey" placeholder; keeps the CRDT
author id distinct (unifying it would rewrite authorship — a later decision).
- Test: deterministic identity from the seed hex, matches commons_core, rejects
malformed hex. 5 tests green; app_seeds analyzes clean (0 errors).