PoW and outbound Nostr events
June 20, 2026 · View on GitHub
This document describes how Mostrix applies NIP-13 proof-of-work to events it publishes toward Mostro and related flows. It is intended for contributors and AI-assisted codegen so outbound signing behavior stays consistent with the Mostro instance policy.
Source of difficulty
- The Mostro instance status event (kind 38385) includes optional tags:
pow— unsigned integer; parsed intoMostroInstanceInfo.pow(Option<u32>).protocol_version—"1"or"2"; drivestransport_from_instance→AppState.transport. Missing tag → legacy GiftWrap.
- There is no
powfield inSettingsor in the generatedsettings.tomltemplate. - Optional
pow_first_contacton kind 38385 (when the daemon publishes it): parsed intoMostroInstanceInfo.pow_first_contact. When absent, effective first-contact toll = basepow. - Effective bits for signing:
nostr_pow_from_instancemaps basepow;nostr_pow_for_protocol_dmselects per-action bits forsend_dm.
Cached instance info at runtime
AppState.mostro_infoholds the latest fetchedMostroInstanceInfo.AppState.transportmirrors resolvedTransport. Updated viaset_mostro_info.EnterKeyContextthreadsmostro_infointo async work without re-fetching per message.send_dmtakesmostro_instance: Option<&MostroInstanceInfo>and computespow = nostr_pow_for_protocol_dm(mostro_instance, action)once per send.
At startup (and on reload/reconnect via dm_transport_for_mostro), instance info is fetched before the DM listener spawns when relays are reachable.
Protocol v2 (NIP-44 direct) — outbound
send_dm uses wrap_message_with from mostro-core:
- v1 (
GiftWrap): PoW on the outer kind-1059 envelope (viamostro-corenip59::wrap_message). - v2 (
Nip44Direct): PoW on the signed kind-14 event (WrapOptions.pow). - NIP-40 expiration: default now + 30 days on v2 when the caller passes
expiration: None(default_dm_expiration).
First-contact actions on strict v2 nodes may need higher PoW than instance pow (pow_first_contact on the daemon). Mostrix applies max(pow, pow_first_contact) on v2 for NewOrder, TakeBuy, and TakeSell via nostr_pow_for_protocol_dm. When the kind-38385 pow_first_contact tag is absent, the effective toll defaults to base pow (same as the daemon).
Chat vs protocol PoW
- Protocol DMs toward Mostro: instance PoW + transport from
transport_from_instance+wrap_message_with. - Shared-key chat (admin dispute, user order, observer):
mostro_core::chatGiftWrap — no PoW.
Call sites (high level)
Order flows under src/util/order_utils/, admin actions in src/ui/key_handler/admin_handlers.rs, and rating handlers in src/ui/key_handler/message_handlers.rs pass cached mostro_instance into send_dm.
Related docs
- STARTUP_AND_CONFIG.md — instance info at boot and on reconnect
- MESSAGE_FLOW_AND_PROTOCOL.md — protocol overview and v2 status