diff --git a/crates/core/src/node.rs b/crates/core/src/node.rs index 44fb070..02940d6 100644 --- a/crates/core/src/node.rs +++ b/crates/core/src/node.rs @@ -1014,6 +1014,7 @@ impl Node { content, intent, attachment_data, + None, ).await } @@ -1038,9 +1039,43 @@ impl Node { content, intent, attachment_data, + None, ).await } + /// FoF Layer 2: create a Mode 2 post (public body, FoF-gated + /// comments). Intent is Public; the FoF gating block is built + /// from the default persona's keyring and embedded in + /// `Post.fof_gating`. The author retains the per-post CEK locally + /// for decrypting their own comments later. + /// + /// Returns `(post_id, post, visibility, cek)`. `visibility` is + /// always Public for Mode 2. + pub async fn create_post_with_fof_comments( + &self, + content: String, + attachment_data: Vec<(Vec, String)>, + ) -> anyhow::Result<(PostId, Post, PostVisibility, [u8; 32])> { + // Build the gating block from the default persona's keyring. + let built = { + let storage = self.storage.get().await; + crate::fof::build_fof_comment_gating(&*storage, &self.default_posting_id)? + .ok_or_else(|| anyhow::anyhow!( + "default persona has no V_me; rotate or recreate before FoF posts" + ))? + }; + let cek = built.cek; + let (post_id, post, visibility) = self.create_post_inner( + &self.default_posting_id, + &self.default_posting_secret, + content, + VisibilityIntent::Public, + attachment_data, + Some(built.gating), + ).await?; + Ok((post_id, post, visibility, cek)) + } + async fn create_post_inner( &self, posting_id: &NodeId, @@ -1048,6 +1083,7 @@ impl Node { content: String, intent: VisibilityIntent, attachment_data: Vec<(Vec, String)>, + fof_gating: Option, ) -> anyhow::Result<(PostId, Post, PostVisibility)> { // Validate attachments if attachment_data.len() > 4 { @@ -1163,7 +1199,7 @@ impl Node { content: final_content, attachments, timestamp_ms: now, - fof_gating: None, + fof_gating, }; let post_id = compute_post_id(&post);