Slice 4 of Block 2. The pure rule was already parameterised; this adds the
policy, cold-start and UI around it.
- commons_core: WotParams (sigQty/stepMax/sigValidity, Duniter defaults) +
WebOfTrust.membersWith; npubToHex helper (NIP-19 decode) for adding roots.
- WotSettings (keystore): the active parameters, configurable — a young
network loosens them and tightens as it grows, as Ğ1 did. Defaults Duniter.
- TrustReferents: the bootstrap 'seeds' membership is measured from — a
bundled asset (empty until real founders are curated, no invented keys)
unioned with referents the user adds by npub/QR. Honest cold-start.
- TrustCubit: computes the full membership verdict against referents+params
alongside the personal circle, and exposes a TrustTier (networkMember >
inYourCircle > vouched > unknown). Certifications issued with the active
validity (they expire and renew, Duniter rule).
- UI: chat trust badge by tier; a 'Network of trust' screen (manage roots +
advanced params) reached from the profile. i18n en/es/pt/ast.
Tests: WotParams/membersWith, npubToHex, TrustReferents, WotSettings, and
TrustCubit tiers/membership. Resolves the WoT-parameters decision
(open-decisions §B). Trust net stays empty/undetermined until seeded — by
design; users bootstrap their own roots.
The vecino/market view had three gaps:
- Publishing inventory duplicated the listing. discover() appended every
offer the relay streamed, but relays legitimately resend an addressable
NIP-99 event (stored copy + live echo on publish). Merge by
(authorPubkeyHex, id) so each offer appears once; two authors reusing a
lot id stay distinct.
- No product detail. Tapping a card jumped straight to chat. Add a
read-only "product page" (MarketOfferDetailScreen) with title, mode,
category, eco badge, coarse location, price, a "Shared by" seller
section (profile fetched by pubkey) and the message button. Reached via
a new /market/offer route; cards are now fully tappable. Shows only what
the network already carries — no photos/description added to the wire.
- No filters. Add a filter bar (type, category, eco) mirroring inventory,
each chip group shown only when a discovered offer can match it. To make
the eco filter real, publish the organic flag on the wire: Offer.isOrganic
-> NIP-99 'organic' tag -> OfferMapper -> ShareableLot.
Tests: commons_core organic round-trip; app-side dedup, two-author
separation, filter logic, mapper passthrough; detail-screen widget tests.
i18n keys added to en/es/pt/ast and slang regenerated.
Discovery searched the full 5-char geohash (~2.4 km) with an exact Nostr tag
match, so two devices in adjacent cells never found each other even though
publish emits the full NIP-52 prefix ladder. Search now coarsens the area to a
configurable precision (SocialSettings.searchPrecision, default 4 ~20 km) via
searchPrefix(), with a human-worded 'how far' selector (Very close / Around
here / My region -> 5/4/3) in the market setup sheet.
Also: surface a clear 'couldn't reach the servers' message when no relay
accepts a share (instead of 'shared 0'), and add wss://relay.comunes.org as the
first default relay (reliable community home; public relays are backup).
Tests: settings precision (default/clamp/roundtrip), searchPrefix helper,
neighbour-cell discovery regression (offers_cubit + commons_core nostr
transports), and the range-selector widget test.
The optional Ğ1 (free currency) integration, per g1-integration.md.
- Profile: an optional 'Ğ1 address' field, persisted locally and published in
your NIP-01 kind:0 metadata (separate from the Nostr key).
- commons_core: UserProfile/ProfileTransport carry a 'g1' field (round-trip
tested).
- Chat: when the peer published a Ğ1 address, a 'Pay in Ğ1' app-bar action opens
their wallet (url_launcher) so value moves in the wallet, not the app — copies
the address as a fallback. No payment rails in-app (sharing-model §4.3).
NOTE (vjrj): the wallet deep-link uses the Cesium web wallet as a universal
fallback; swap the URI in chat_screen._payG1 for the Ğ1nkgo/Ğecko scheme you
prefer. Analyzer clean (both packages); profile transport test covers g1.
Drawer 'Profile' is now live.
- commons_core: ProfileTransport + UserProfile + NostrProfileTransport (kind:0
publish/fetch), exposed as SocialSession.profile. Round-trip tested against the
in-process relay (fast dart test).
- app: ProfileStore (local name/about, keystore) + ProfileScreen — shows your
shareable npub with copy, edits display name + 'about', saves locally and (when
online) publishes kind:0 so peers can recognise you instead of a raw key.
- Drawer 'Profile' -> /profile (gated like Market/Chat).
- i18n en/es/pt. ProfileStore + profile transport covered by plain/dart tests.
Analyzer clean (both packages).
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.
Pure Dart lat/lon -> low-precision geohash (public-domain algorithm), the piece
the coarse-area capture needs so people never type a code. Precision-controlled
(5 chars ~ ±2.4 km). Tested against the canonical u4pruydqqvj vector + prefix/
proximity properties. 5 tests green.
Second production slice: the concrete Nostr backend on the shared connection,
using the vetted nostr package's crypto (NIP-44 spec-vector tested, gift wrap).
- NostrConnection: one dart:io WebSocket + identity + EVENT/OK, REQ/EOSE via
the library's Event/Filter/Request. Pure Dart, no Flutter.
- NostrOfferTransport (NIP-99 kind 30402) + Nip99Codec: geohash coarsened to a
NIP-52 prefix ladder on the wire; Offer stays inventory/location-agnostic.
- NostrMessageTransport (NIP-17): send/inbox via the library's DirectMessage
gift-wrap onion.
- NostrTrustTransport (custom addressable kind 30777): certify/revoke/discover.
- MiniRelay test support + integration tests: offers, messaging (metadata
privacy), WoT membership + revoke, and trust-filters-offers — all over ONE
shared connection, hermetic in-process relay.
Renamed the core offer lifecycle enum OfferStatus -> OfferLifecycle to avoid
colliding with app_seeds' sharing-intent OfferStatus (no Block 1 code touched;
app_seeds analyzes clean, 0 errors). commons_core: 55 tests green.