Throwaway research spike (spike/ branch, outside pub workspace, no prod deps touched). Validates the open Block 2 decisions before committing a funded round: 1. Deterministic one-way secp256k1 (Nostr) key derived from the Ğ1 root seed via HKDF — user still backs up ONE thing. Reproducible + signs (BIP340). 2. OfferTransport abstraction over Nostr NIP-99 (kind 30402); Offer stays inventory/location-agnostic, geohash coarsened on the wire (tested). 3. publish->discover-by-geohash proven end-to-end against an in-process hermetic mini-relay (~34ms round-trip). Findings + risks + recommendation in docs/design/spike-block2-findings.md. Block 1 suite untouched. 14 tests green, analyzer clean.
90 lines
2.8 KiB
Dart
90 lines
2.8 KiB
Dart
/// The four (well, five) reciprocity modes from sharing-model.md §3, plus the
|
|
/// generic `lend` the core reserves for the future "library of things".
|
|
enum OfferType { gift, exchange, sale, wanted, lend }
|
|
|
|
enum OfferStatus { active, reserved, closed }
|
|
|
|
/// A publishable offer — the "shop window", deliberately DECOUPLED from Lot /
|
|
/// inventory (sharing-model.md §2, core-domain-boundary.md §4.3).
|
|
///
|
|
/// It carries only a chosen, denormalised *summary* of what's offered — never a
|
|
/// foreign key into the seed tables, never the full inventory, never an exact
|
|
/// address. This is the privacy seam: the core can publish and sync offers
|
|
/// WITHOUT understanding seeds. Everything here is what the person elected to
|
|
/// reveal.
|
|
class Offer {
|
|
const Offer({
|
|
required this.id,
|
|
required this.authorPubkeyHex,
|
|
required this.summary,
|
|
required this.type,
|
|
required this.approxGeohash,
|
|
this.status = OfferStatus.active,
|
|
this.category,
|
|
this.priceAmount,
|
|
this.priceCurrency,
|
|
this.exchangeTerms,
|
|
this.radiusKm,
|
|
this.imageUrl,
|
|
this.expiresAt,
|
|
});
|
|
|
|
/// Stable per-offer identifier (NIP-99 `d` tag → addressable event).
|
|
final String id;
|
|
|
|
/// The publishing identity (BIP340 x-only pubkey hex). Derived, per Q1.
|
|
final String authorPubkeyHex;
|
|
|
|
/// Human summary the author chose to publish (e.g. "Tomate rosa de Barbastro").
|
|
final String summary;
|
|
|
|
final OfferType type;
|
|
final OfferStatus status;
|
|
|
|
/// Free-text category, prefilled in-app from Species.family but opaque here.
|
|
final String? category;
|
|
|
|
/// LOW-PRECISION geohash only (sharing-model.md §2: "10 km near you", never
|
|
/// exact coordinates). The spike enforces a max length in the transport.
|
|
final String approxGeohash;
|
|
final int? radiusKm;
|
|
|
|
final num? priceAmount; // only for sale
|
|
final String? priceCurrency; // ISO 4217 or a community currency (e.g. "G1")
|
|
final String? exchangeTerms; // only for exchange
|
|
|
|
final String? imageUrl;
|
|
final DateTime? expiresAt;
|
|
}
|
|
|
|
/// Result of publishing to a transport.
|
|
class PublishResult {
|
|
const PublishResult({
|
|
required this.accepted,
|
|
required this.transportRef,
|
|
this.message = '',
|
|
});
|
|
|
|
/// Whether the relay/instance accepted the event.
|
|
final bool accepted;
|
|
|
|
/// The transport-side id (Nostr event id / ActivityPub object id).
|
|
final String transportRef;
|
|
final String message;
|
|
}
|
|
|
|
/// A discovery query: coarse location + optional facets. Note there is NO way to
|
|
/// ask for "everything from person X's inventory" — you can only browse the
|
|
/// public shop window by area and kind.
|
|
class DiscoveryQuery {
|
|
const DiscoveryQuery({
|
|
required this.geohashPrefix,
|
|
this.types = const {},
|
|
this.limit = 100,
|
|
});
|
|
|
|
/// Coarse geohash prefix to search near (e.g. "u09" ≈ tens of km).
|
|
final String geohashPrefix;
|
|
final Set<OfferType> types;
|
|
final int limit;
|
|
}
|