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.
The other half of the market: share your marked lots as offers.
- VarietyRepository.shareableLots(): non-private lots WITH their id (stable,
updatable offer id) + the variety label. Private lots never leave the device.
- OffersCubit.publishLots(): maps each via OfferMapper and publishes, tagged
with the coarse area + signed by the derived key; counts acceptances; no-op
offline or with no area.
- Market screen: a 'share my seeds' action (online only) — publishes, then
re-discovers to show them; 'mark some seeds first' when nothing's shared.
- i18n en/es/pt.
14 tests green (repo query, cubit publish, market UI); analyzer clean for new code.
Persistence + wiring the offers screen needs, per the product decisions:
- SocialSettings (keystore-backed, no plaintext): coarse area geohash +
configurable relay URLs, both empty by default (no bundled public relays —
don't leak metadata to third parties). Registered in DI.
- createOffersCubit(): opens a SocialSession on the first configured relay when
online, else a null-transport cubit that degrades gracefully. OffersCubit now
closes the owning session on dispose.
4 settings tests green; offers/social suites still green.
The testable core of the offers UI slice, transport-agnostic:
- OfferMapper: pure app-sharing-intent (OfferStatus) -> network Offer/OfferType
mapping; keeps price only for sales, refuses to publish private lots.
- OffersCubit: discover(geohash) streams results; publish() reports the verdict;
depends on the OfferTransport interface (fake in tests) and degrades
gracefully offline (null transport = social layer unavailable, never throws).
8 tests green (fake in-memory transport, no relay). Screen/routing/i18n come
next — they need two product decisions (coarse-area capture, relay list).