Summary
Two related write-path hygiene gaps found in the nostr audit (projects/mobee-seller-node/NOSTR-AUDIT.md, N-3/N-8). The sharper one is money-adjacent: the buyer's ecash gift-wrap write fires before NIP-42 auth can complete, and the SDK will not re-auth-and-resend a rejected write — so an auth-gated write racing the handshake is dropped, not retried. The neighbouring receipt write gates on auth explicitly; the payment write does not. Line cites are origin/dev-era e14942a.
N-3 — payment gift-wrap does not wait for NIP-42 auth (money, VERIFY-NEEDED trigger)
payment_send.rs:207-216 (send_payment, the buyer→seller kind-1059 payment):
let client = Client::new(self.keys.clone());
client.add_relay(&self.relay).await?;
client.connect().await; // no wait_for_connection
let output = client.send_event_to([&self.relay], &gift_wrap).await; // fired immediately
client.disconnect().await;
- Auto-auth is on by default (
nostr-relay-pool 0.44.1 pool/options.rs:21), so the client will answer a challenge — but asynchronously, on the inbound RelayMessage::Auth path (relay/inner.rs:1057-1064).
wait_for_ok returns the first OK for the event verbatim and does not re-authenticate-and-resend on an auth-required rejection (relay/inner.rs:1379-1415).
- So if the EVENT is processed before the auth round-trip finishes, an auth-gated write is rejected and dropped;
disconnect() follows immediately.
Contrast the receipt write (authorize_pay.rs:697-757), which subscribes notifications before connect, wait_for_connection(20s), and gates the send on Authenticated (:726), failing closed otherwise — because "mobee-relay requires NIP-42 AUTH for ALL writes" (:675).
The payment path works in the field (25+ paid), so the race usually wins. VERIFY-NEEDED: does mobee-relay auth-gate kind-1059 writes (not just the p-gated read)? If yes, this is a latent silent payment-write drop under connection latency. Fix: mirror the receipt path's wait_for_nip42_auth gate on the payment write (and set automatic_authentication(true) explicitly, as the receipt/seller paths do, to guard against option drift).
N-8 — no timeout on buyer write awaits
client.connect() + send_event_to have no per-call timeout in publish_draft_async (job_lifecycle.rs:1444-1447), payment_send (:211-215), and profile (:264/:318) — only fetch_events carries one. A hung relay socket blocks the MCP/CLI call indefinitely. The buzz presence loop's send_event (buzz.rs:439) is in the same un-timed class. This is the buyer-side analogue of the seller heartbeat/publish park in #173 (which covers the seller loop); authorize_pay already demonstrates the fix pattern (wait_for_connection(20s) + auth timeout) — it just is not applied uniformly.
Read-only audit; no code changed. Cross-ref #173 (seller-loop park), and the shared write path this would naturally live in under the buyer-client-consolidation issue.
Summary
Two related write-path hygiene gaps found in the nostr audit (
projects/mobee-seller-node/NOSTR-AUDIT.md, N-3/N-8). The sharper one is money-adjacent: the buyer's ecash gift-wrap write fires before NIP-42 auth can complete, and the SDK will not re-auth-and-resend a rejected write — so an auth-gated write racing the handshake is dropped, not retried. The neighbouring receipt write gates on auth explicitly; the payment write does not. Line cites areorigin/dev-erae14942a.N-3 — payment gift-wrap does not wait for NIP-42 auth (money, VERIFY-NEEDED trigger)
payment_send.rs:207-216(send_payment, the buyer→seller kind-1059 payment):nostr-relay-pool 0.44.1 pool/options.rs:21), so the client will answer a challenge — but asynchronously, on the inboundRelayMessage::Authpath (relay/inner.rs:1057-1064).wait_for_okreturns the first OK for the event verbatim and does not re-authenticate-and-resend on anauth-requiredrejection (relay/inner.rs:1379-1415).disconnect()follows immediately.Contrast the receipt write (
authorize_pay.rs:697-757), which subscribes notifications before connect,wait_for_connection(20s), and gates the send onAuthenticated(:726), failing closed otherwise — because "mobee-relay requires NIP-42 AUTH for ALL writes" (:675).The payment path works in the field (25+ paid), so the race usually wins. VERIFY-NEEDED: does mobee-relay auth-gate kind-1059 writes (not just the p-gated read)? If yes, this is a latent silent payment-write drop under connection latency. Fix: mirror the receipt path's
wait_for_nip42_authgate on the payment write (and setautomatic_authentication(true)explicitly, as the receipt/seller paths do, to guard against option drift).N-8 — no timeout on buyer write awaits
client.connect()+send_event_tohave no per-call timeout inpublish_draft_async(job_lifecycle.rs:1444-1447),payment_send(:211-215), andprofile(:264/:318) — onlyfetch_eventscarries one. A hung relay socket blocks the MCP/CLI call indefinitely. The buzz presence loop'ssend_event(buzz.rs:439) is in the same un-timed class. This is the buyer-side analogue of the seller heartbeat/publish park in #173 (which covers the seller loop);authorize_payalready demonstrates the fix pattern (wait_for_connection(20s)+ auth timeout) — it just is not applied uniformly.Read-only audit; no code changed. Cross-ref #173 (seller-loop park), and the shared write path this would naturally live in under the buyer-client-consolidation issue.