# @suaveplan/network-discovery-gundb - Browser-tier GunDB DiscoveryStrategy: gossip-over-WebSocket peer discovery via Gun. Relay list REQUIRED — no public-relay fallback. Mandatory Ed25519-signed peer announces. Zod wire-payload validation. Constant-time peer-ID compare. AbortSignal + EventBusLike + ServiceRegistry + OTel + Zod boundaries. ## Summary Browser-tier `DiscoveryStrategy` for `@suaveplan/network-bootstrap` chains. Wraps a [GunDB](https://gun.eco) instance — gossip-over- WebSocket decentralised graph database — and exposes a deduped, capped peer set to the bootstrap pipeline. GunDB is flagged security-fragile upstream; this release tightens every relevant attack surface. ## Documentation tree - [gundb-discovery](./src/core/gundb-discovery/gundb-discovery.md) — _(no submodules)_ - [gundb-discovery](./src/core/gundb-discovery/gundb-discovery.md) — _(no submodules)_ - [errors](./src/errors/errors.md) — _(no submodules)_ - [errors](./src/errors/errors.md) — _(no submodules)_ - [otel](./src/otel/otel.md) — _(no submodules)_ - [otel](./src/otel/otel.md) — _(no submodules)_ - [plugin](./src/plugin/plugin.md) — _(no submodules)_ - [plugin](./src/plugin/plugin.md) — _(no submodules)_ - [signing](./src/signing/signing.md) — _(no submodules)_ - [signing](./src/signing/signing.md) — _(no submodules)_ - [transport](./src/transport/transport.md) — _(no submodules)_ - [envelope-signing](./src/transport/envelope-signing.md) — Provides the cryptographic helpers used by `GunDBTransport` to sign every outgoing `MessageEnvelope` and verify every i… - [transport](./src/transport/transport.md) — _(no submodules)_ - [types](./src/types/types.md) — _(no submodules)_ - [types](./src/types/types.md) — _(no submodules)_ - [gundb-discovery](./src/core/gundb-discovery/gundb-discovery.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [plugin](./src/plugin/plugin.md) — _(no submodules)_- [plugin](./src/plugin/plugin.md) — _(no submodules)_- [signing](./src/signing/signing.md) — _(no submodules)_- [signing](./src/signing/signing.md) — _(no submodules)_- [transport](./src/transport/transport.md) — _(no submodules)_- [envelope-signing](./src/transport/envelope-signing.md) — Provides the cryptographic helpers used by `GunDBTransport` to sign every outgoing `MessageEnvelope` and verify every i… - [transport](./src/transport/transport.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [gundb-discovery](./src/core/gundb-discovery/gundb-discovery.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [plugin](./src/plugin/plugin.md) — _(no submodules)_- [plugin](./src/plugin/plugin.md) — _(no submodules)_- [signing](./src/signing/signing.md) — _(no submodules)_- [signing](./src/signing/signing.md) — _(no submodules)_- [transport](./src/transport/transport.md) — _(no submodules)_- [envelope-signing](./src/transport/envelope-signing.md) — Provides the cryptographic helpers used by `GunDBTransport` to sign every outgoing `MessageEnvelope` and verify every i… - [transport](./src/transport/transport.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [gundb-discovery](./src/core/gundb-discovery/gundb-discovery.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_- [plugin](./src/plugin/plugin.md) — _(no submodules)_- [plugin](./src/plugin/plugin.md) — _(no submodules)_- [signing](./src/signing/signing.md) — _(no submodules)_- [signing](./src/signing/signing.md) — _(no submodules)_- [transport](./src/transport/transport.md) — _(no submodules)_- [envelope-signing](./src/transport/envelope-signing.md) — Provides the cryptographic helpers used by `GunDBTransport` to sign every outgoing `MessageEnvelope` and verify every i… - [transport](./src/transport/transport.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_- [gundb-discovery](./src/core/gundb-discovery/gundb-discovery.md) — _(no submodules)_- [errors](./src/errors/errors.md) — _(no submodules)_ - [errors](./src/errors/errors.md) — _(no submodules)_- [otel](./src/otel/otel.md) — _(no submodules)_ - [otel](./src/otel/otel.md) — _(no submodules)_- [plugin](./src/plugin/plugin.md) — _(no submodules)_ - [plugin](./src/plugin/plugin.md) — _(no submodules)_- [signing](./src/signing/signing.md) — _(no submodules)_ - [signing](./src/signing/signing.md) — _(no submodules)_- [transport](./src/transport/transport.md) — _(no submodules)_ - [envelope-signing](./src/transport/envelope-signing.md) — Provides the cryptographic helpers used by `GunDBTransport` to sign every outgoing `MessageEnvelope` and verify every i… - [transport](./src/transport/transport.md) — _(no submodules)_- [types](./src/types/types.md) — _(no submodules)_ - [types](./src/types/types.md) — _(no submodules)_ ## Why this package - Plug-and-play `DiscoveryStrategy` slot for network bootstrap chains. Works alongside `@suaveplan/network-discovery-peerjs`, `@suaveplan/network-discovery-trystero`, `@suaveplan/network-discovery-webtorrent`, and `@suaveplan/network-discovery-crdt`. - **Relay list REQUIRED.** Upstream's public-relay fallback (`https://gun-manhattan.herokuapp.com/gun`) is removed. Callers must supply their own relays. - **Every outbound announce is Ed25519-signed.** The local peer's 32-byte secret is the contract input. - **Every inbound announce is Zod-parsed AND Ed25519-verified before admission.** Failures silently drop the payload and increment `gundb.invalid_messages_total{reason}` / `gundb.signature_rejected_total{reason}` — a malicious peer cannot crash a node by gossiping garbage. - **Constant-time peer-ID compare** via `constantTimeEqual` from `@suaveplan/crypto-utils`. - Injectable `gunFactory` — production wires the real `Gun(config)`; tests bind a deterministic `GunNodeLike` fake (no `vi.mock` ever used). - Typed `EventBusLike` integration. - Optional `@suaveplan/service-discovery` `ServiceRegistry` mirroring. - OTel surface: `disc_gundb.start` span; counters `peers_discovered_total{outcome}`, `gundb.invalid_messages_total{reason}`, `gundb.signature_rejected_total{reason}`, `disc_gundb.relay_errors_total{relay}`; histogram `gundb_announce_duration_ms`. - `AbortSignal` honoured on every async public method. - 100% test + branch + function + line coverage. ## Basic example ```ts import { GunDBDiscovery, publicKeyHex } from "@suaveplan/network-discovery-gundb"; const signingKey = crypto.getRandomValues(new Uint8Array(32)); const localPeerId = publicKeyHex(signingKey); const discovery = new GunDBDiscovery({ peers: ["wss://my-private-relay.example.com/gun"], gunFactory: async (cfg) => { const { default: Gun } = await import("gun"); return Gun({ peers: cfg.peers as string[] }) as unknown as GunNodeLike; }, localPeerId, signingKey, }); await discovery.start(); const peers = await discovery.discover(5_000); console.log(`found ${peers.length} peers`); await discovery.stop(); ``` ## Advanced example ```ts import { EventBus } from "@suaveplan/events"; import { ServiceRegistry } from "@suaveplan/service-discovery"; import { DiscoveryChain } from "@suaveplan/network-bootstrap"; import { GunDBDiscovery, publicKeyHex, type GunDBEventMap, } from "@suaveplan/network-discovery-gundb"; const bus = new EventBus(); const registry = new ServiceRegistry(); const signingKey = crypto.getRandomValues(new Uint8Array(32)); bus.on("gundb:peer-discovered", ({ peer }) => { console.log("peer:", peer.id, peer.addresses); }); bus.on("gundb:relay-error", ({ reason }) => { console.warn("relay:", reason); }); const discovery = new GunDBDiscovery({ peers: [ "wss://relay-a.example.com/gun", "wss://relay-b.example.com/gun", ], room: "my-app/v1", maxPeers: 256, announceIntervalMs: 15_000, gunFactory: async (cfg) => { const { default: Gun } = await import("gun"); return Gun({ peers: cfg.peers as string[], localStorage: cfg.localStorage, }) as unknown as GunNodeLike; }, localPeerId: publicKeyHex(signingKey), signingKey, bus, registry, }); const chain = new DiscoveryChain([discovery]); const peers = await chain.discover(5_000); ``` ## API See sibling co-located markdown: - [`src/types/types.md`](./src/types/types.md) - [`src/errors/errors.md`](./src/errors/errors.md) - [`src/otel/otel.md`](./src/otel/otel.md) - [`src/signing/signing.md`](./src/signing/signing.md) - [`src/core/gundb-discovery/gundb-discovery.md`](./src/core/gundb-discovery/gundb-discovery.md) ## License MIT