spike(block2): de-risk social layer — key derivation, OfferTransport, NIP-99 round-trip
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.
This commit is contained in:
parent
6eb1517ffb
commit
49b872b405
16 changed files with 1284 additions and 0 deletions
90
spike/block2_spike/lib/src/offer.dart
Normal file
90
spike/block2_spike/lib/src/offer.dart
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/// 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue