Skip to main content

Module service

Module service 

Source
Expand description

Orchestration that ties the Community send/delete primitives to persistence, with multi-account safety. This is the layer Tauri commands are thin wrappers over: it publishes a message AND retains its ephemeral key (so the sender can later delete it), and deletes by loading that retained key back.

Every method is SessionGuard-gated: a swap_session can happen at any await point, and persisting an ephemeral secret (or reading one) must never cross into the wrong account’s DB.

Structs§

BaseCatchup
Outcome of catch_up_server_root: the base epoch reached, and whether an AUTHORIZED base rotation EXCLUDED us (a read-cut / private ban). removed is the catch-all “you’ve been removed” signal for a cryptographically cut member who can no longer read the banlist to learn it the normal way.
CommunityCapabilities
The local user’s effective management capabilities in a community, resolved purely by the role engine (positions + permission bits; the owner is just the role at position 0 — NOTHING is owner-hardcoded). The frontend gates each management affordance on the matching bit, so an admin whose role carries a permission gets the exact same affordance as the owner.

Enums§

RekeyOutcome
The result of applying a received channel Rekey (3303).

Constants§

MAX_COMMUNITIES
Max communities a device may hold locally. The synced Community List is a single NIP-44 event (65 KB plaintext); past the cap even the slimmed list can’t encrypt, so a NEW join/create is rejected above it (the user leaves one to make room).

Functions§

accept_invite
Accept a parked invite and persist the member-view Community (the user-consented half of the carrier — the inbound handler only parks invites; this is reached from an explicit accept command). Guards against id-collision overwrites:
accept_public_invite
Accept a fetched public-invite bundle: reject if expired, join via the guarded member-save (caps + id-collision checks), then patch in the preview’s display metadata (description/icon) so the new member sees them immediately.
am_i_banned
Is the local user in this community’s (folded, cached) banlist? Drives BAN self-removal: a banned member tears down locally (drop the community keys + wipe local chat data) exactly like a kick, but CANNOT rejoin — re-detecting the ban on any later sync re-removes them, and admins can’t invite a banned npub. Reads the cached banlist, so refresh it via fetch_and_apply_banlist first for an authoritative (realtime or boot) check.
apply_channel_rekey
Apply a received, already-opened channel Rekey (super::rekey::open_rekey_event) for community.
apply_server_root_rekey
Apply a received, already-opened SERVER-ROOT (base) Rekey for community — the base counterpart to apply_channel_rekey. Verifies the rotator’s server-wide rotation authority (BAN, “role-based, not owner-only”), checks continuity against the held prior ROOT (when held), finds + opens MY ServerRoot-scope blob, and commits the new root via the atomic base head+archive write. The new root reaches me ONLY through my ECDH blob — if I was removed in this rotation I find no blob (NotARecipient) and recover nothing. SessionGuard-gated; synchronous (one guard + the write re-check suffice, same as apply_channel_rekey).
build_presence
Announce presence (join/leave) into a channel: a kind-3306 inner signed by the active identity, published under a fresh ephemeral outer. Content is "leave", plain "join", or — for a join via a public invite — a small JSON {"by":"<inviter npub>","l":"<label>"} carrying attribution (which link/source brought this member; members-only). Client best-practice (not enforced); no deletion key retained. Callers treat failure as non-fatal. attribution = Some((inviter_npub, label)) on an invite-join, else None. Build + sign a presence (3306) inner event WITHOUT publishing. Lets the caller record the local system event first (memory→DB, like an outgoing message) and publish in the background — the relay echo then dedups by this inner’s id. inner.id is the system-event dedup key.
caller_can_manage_role_id
Can the local caller grant/revoke role_id — i.e. do they hold MANAGE_ROLES AND outrank that role’s position? The crown’s gate, expressed as the POSITION rule (NOT an owner check): the owner is just position 0, so in the single-@admin-role MVP this resolves to “owner only” because the @admin role sits directly below position 0 — but it generalizes to any role hierarchy. false if the role is unknown.
caller_can_manage_roles
True iff the local user may manage roles — i.e. holds the MANAGE_ROLES permission. Permission-based, NOT a hardcoded owner check: the owner is simply the uppermost role and holds every permission; any member granted a role carrying MANAGE_ROLES qualifies just the same.
caller_capabilities
caller_has_permission
Does the local user hold permission in this community? The generalized caller_can_manage_roles: owner = supreme (every bit), otherwise the union of their granted roles’ bits (the role engine). Drives both the capability report and the producer-side authority gates — no hardcoded owner check.
can_moderation_hide
Can actor_hex moderation-hide a message authored by author_hex in this community? True iff the actor holds MANAGE_MESSAGES and strictly outranks the author (the owner is unhideable). This is the SINGLE source of truth for moderation authority — both the publish gate (publish_owner_hide) and the UI affordance (get_message_delete_options) call it, so the button shown can never disagree with what the publish will actually allow.
catch_up_channel_rekeys
Catch a channel up to the latest epoch it is still a recipient of (windowed scan): fetch every rekey published since our held epoch and apply the chain. Returns the channel’s new current epoch. Idempotent + cheap on the steady state (no new rotations → one empty-window fetch → returns the held epoch). 3303s are addressed by the server-root-derived rekey_pseudonym, so this is a SEPARATE fetch from the channel message plane (the exception).
catch_up_server_root
create_community
Create a brand-new Community end-to-end: mint keys + the default channel, persist it locally, and publish its GroupRoot + ChannelMetadata to the Community’s relays. Returns the created Community. (The caller then runs the subscription refresh so it starts receiving.)
create_public_invite
delete_message
Delete a message the local user previously sent, by its INNER message id (what the UI holds). Loads the retained ephemeral key + the outer event id it points at, then NIP-09-deletes that outer event. Errors if no key is retained (not ours, or already deleted).
dissolve_community
fetch_and_apply_banlist
fetch_and_apply_control
Fetch the control plane ONCE and apply every slice — banlist, roles, invite links, metadata — from a single REQ + single fold. Sync/join/boot call THIS instead of the four fetch_and_apply_* in sequence (which was four identical REQs). Banlist is applied first so a caller’s subsequent am_i_banned sees the freshest list. Each slice is best-effort; one failing doesn’t abort the rest. (Solo callers that need a single slice — e.g. revoke refreshing invite links — still use the individual fetch_and_apply_*.)
fetch_and_apply_invite_links
Fetch the control plane and apply the folded invite-link AGGREGATE locally: UNION the locators of every per-creator vsk=8 edition whose creator held CREATE_INVITE in the AUTHORIZED roster (the keyless gate, same shape as the banlist’s BAN check), advancing each authorized creator’s head (refuse-downgrade). The union is the source of truth for the Public/Private mode (is_public) + the metrics — NOT join-gating (joining is envelope-only). Returns the aggregate set (empty = Private).
fetch_and_apply_metadata
Fetch the Community’s control plane and apply folded METADATA edits locally: the GroupRoot (vsk=0 — community name/description/icon/banner) and each ChannelMetadata (vsk=2 — channel name). An edition applies only if its signer held MANAGE_METADATA in the AUTHORIZED roster (the keyless gate, same as the producer) AND is strictly newer than the head we hold (refuse-downgrade by version). Identity/transport fields (server_root_key, relays, owner_attestation) are NEVER taken from a metadata edit — a manage-metadata admin edits DISPLAY, not the community’s identity. Best-effort: returns Ok even when nothing applied. This is what makes an owner/admin’s edit sync to every member.
fetch_and_apply_roles
Fetch the Community’s role graph (real-npub control editions, kind 3308) and fold it into the local roster. Fetches by the server-root pseudonym (not by author — the outer is ephemeral), opens each edition under the server-root key, and folds: verify authorship, bind entity↔content, version-fold, quarantine gaps. Advances each entity’s monotonic head (the per-entity refuse-downgrade floor) and refreshes the roster cache. Returns the folded roster.
fetch_public_invite
Fetch + decrypt the bundle for a public-invite token from the given bootstrap relays. Queries the addressable coordinate (d = token locator, author = token signer) and verifies the signer, so an impostor squatting the locator is rejected.
grant_role
Grant member a role (requires the MANAGE_ROLES permission). Publishes the per-member Grant event. The member already holds read keys from membership; the roster entry adds write authority, exercised by signing their own control actions, which peers verify against the roster.
is_proven_owner
True iff the local user is the PROVEN owner of this community — derived by verifying the owner attestation against my_public_key() (keyless: the owner is the npub that signed the attestation binding this community_id). The check honest clients use to gate owner-only actions (mint invites, set images) and to render the owner crown.
is_public
The computed Public/Private mode: a community is PUBLIC iff the folded per-creator invite-link aggregate has ≥1 active locator, else PRIVATE. Every member computes the same value from the folded editions, which is what lets it drive rekey-on-removal consistently (Private removals rekey the base to the roster; Public ones don’t — anti-memberlist). Reads the cached aggregate, which is only as fresh as the last successful latest-page sync (fetch_and_apply_invite_links, wired best-effort into the sync path) — a member who only scrolled back, or whose sync failed, can hold a stale mode (which is why revoke_public_invite refreshes the aggregate before deciding to privatize).
latest_invite_preview
Read-only freshen for an invite preview: build the bundle’s ephemeral community, fold the live control plane, and return the LATEST authorized display metadata — never the bundle’s mint-time snapshot (which goes stale the moment metadata is edited; mirrors the website preview). No DB floors and no persistence: the previewer isn’t a member, so there is no local state to anchor. Any failure falls back to the snapshot so a flaky relay can’t blank the preview.
persist_webxdc_signal
Persist an inbound WebXDC peer signal as a kind-30078 event row — the SAME shape the DM peer-advertisement handler writes (content peer-advertisement/peer-left, reference_id = topic, webxdc-topic/webxdc-node-addr tags) — so the miniapp layer’s get_active_peer_advertisements (latest-per-npub, left-tombstone-aware) reads both transports identically. This is what lets a member who closed Vector mid-session rediscover the active players on reopen. Idempotent via event_exists.
preload_community
Warm a community’s primary-channel first page into the RAM preload cache BEFORE the user joins, so accepting opens a populated chat instead of paying the join sync. RAM-only and side-effect- free: builds the member view from the bundle WITHOUT persisting (nothing is stored for a community the user may decline), fetches one page, and stashes it keyed by community id (the fetch also warms the relay connection). Best-effort — any failure just leaves Join to sync normally. Spawn this behind a SessionGuard; promotion on Join re-validates freshness.
publish_banlist
Replace the Community banlist and publish it as a real-npub-signed 3308 EDITION (vsk=4) at the community-scoped banlist locator (keyless; foldable + re-anchorable). banned_hex is the full new list (latest-wins). The actor’s inner signature IS the authority proof; every member re-verifies it held BAN against the authorized roster on receipt. Publish FIRST, then persist locally on success — a failed publish must not leave us enforcing a ban no one else sees.
publish_kick
publish_my_invite_links
Publish the LOCAL user’s OWN invite-link set as a CREATE_INVITE-gated vsk=8 control edition at their per-creator coordinate — one of the per-creator lists members fold into the aggregate active-set. my_locators is the FULL new set of THIS creator’s active link locators (hex; the token in the URL is the secret, never listed). Publish FIRST, then advance the head + merge into the cached aggregate on success (relay-authoritative + phantom-head rule). A creator manages only their own list — no MANAGE_INVITES. Carries the actor’s vac citation so a non-owner creator’s authority is verifiable.
publish_owner_hide
Moderation-hide: publish a 3305 delete for another member’s message, signed by the actor’s REAL npub (keyless). Authority is the inner signature, re-verified by every member against the owner-rooted roster (MANAGE_MESSAGES + a strict outrank of the target’s author). Permanent (the tombstone can’t be un-published).
publish_presence
publish_presence_event
Publish a pre-built presence inner (from build_presence) to the channel’s recipient set.
publish_typing_signal
Publish a typing indicator (3311) into a channel: an inner “typing” event signed by the member, sealed under the channel epoch key like presence. The Community-transport twin of the NIP-17 typing rumor. Ephemeral — never persisted/folded; the latency-sensitive single-attempt path (durable = false), and callers treat failure as non-fatal (a dropped keystroke ping is harmless; the next one ~every few seconds covers it).
publish_webxdc_signal
Publish a WebXDC realtime peer signal (3310) into a channel: an advertisement of the local Iroh node for a Mini App session (node_addr = Some) or a peer-left (node_addr = None). The Community-transport twin of the NIP-17 peer-advertisement/peer-left DM rumors — signed by the member’s real identity (a member can’t forge another player’s presence), sealed under the channel epoch key like presence. Callers treat failure as non-fatal (a missed ad only delays discovery; the next re-advertise covers it).
republish_channel_metadata
Rename a channel and republish its ChannelMetadata as a real-npub 3308 edition (vsk=2) so members fold it via fetch_and_apply_metadata. Keyless authority: the actor must hold MANAGE_CHANNELS (channel edits are a channel-management action; the owner holds every permission). channel_id must be one of community’s channels. Publish-FIRST then persist on success (relay- authoritative, phantom-head-safe — same contract as the community GroupRoot).
republish_community_metadata
Persist edited Community display metadata and republish the GroupRoot as a real-npub 3308 edition (vsk=0) so other members + re-anchoring pick it up. Keyless authority: the actor must hold MANAGE_METADATA (the owner holds every permission). The caller mutates community (name / description / icon / banner) first; this gates, saves it, then publishes the next edition version.
retry_pending_read_cut
Retry an outstanding PRIVATE-community read-cut re-seal, if one is pending. Called from the sync path so a re-seal that failed during a ban (e.g. a relay outage) AUTO-RECOVERS on the owner’s next community sync — no manual re-ban needed. No-op if nothing is pending. If the community has since gone PUBLIC the read-cut is moot (anti-memberlist: a Public ban doesn’t rotate the base), so the stale flag is cleared. Best-effort + idempotent; the re-seal authority (BAN) is enforced by rotate_server_root.
revoke_public_invite
Revoke a public invite: NIP-09-delete the bundle event (by its addressable coordinate, signed by the token-derived key we re-derive from the retained token), forget the token locally, and republish the invite-link registry so the mode tracks reality. If this was the LAST link, the community goes Private → it is re-founded (privatize): the base key is rotated to the observed-participants set, sealing out link-joined lurkers who never spoke. Creator-only: you can only retire YOUR OWN links (the token is held only by its creator); the privatize rekey is BAN-gated + needs a local key.
revoke_role
Revoke a role from member (owner/admin authority) — instant logical (the role record is dropped, so the grant-set check stops honoring their actions). The physical lockout (channel rekey per) is a later step; this only edits the grant. In the MVP a role is permission bits, NOT a channel read key (channels aren’t role-gated), so a revoke needs NO rekey and a bunker account can do it freely. WHEN role-gated channels ship, the rekey-on-revoke path must adopt the same bunker fail-fast guard as publish_banlist/revoke_public_invite (a rekey needs a raw local key).
rotate_channel
Rotate a channel’s key (a channel rekey): mint a fresh-random key for current_epoch + 1, deliver it to recipients as one self-proving 3303 event (epoch + every recipient blob + the prior-epoch commitment + my real-npub authority sig, all in one — the design tenet), publish it, then advance MY local epoch. Returns the new epoch.
send_message
Publish a Community message and retain its ephemeral key in the account DB so the sender can delete it later. Returns the published outer event.
send_signed_message
Publish a message whose inner authorship event was signed externally (via the active signer — local OR bunker) and retain its ephemeral key. Use this from the command layer where client.signer() is available; it gives bunker accounts send parity with DMs. (Local-only callers/tests can use send_message.)
set_member_grant
Set a member’s complete role set (owner/admin authority) and publish their per-member Grant event (vsk=3). Empty role_ids revokes all of that member’s roles. Persists the updated local graph BEFORE the publish await (so our own client reflects it immediately and the write lands in the captured account); the relay echo dedups.
sync_before_admin_write
FRESHEN-BEFORE-WRITE guard for an administrative write (rekey / ban / kick / grant / revoke / metadata): hop any base rotation + fold the LATEST control plane from ALL relays + (for a rekey) ingest channel activity, so the write acts on the freshest reachable truth — not just a stale local view. The demonstrated bug this fixes: privatizing before observing a member’s activity wrongly cut them.