Conversation
…putes solving - added some docs for AI generated code
- Use BUD-01 Blossom auth: kind 24242 with tags t=upload, expiration, x=sha256 (servers reject NIP-98 kind 27235; require human-readable content) - Add Content-Type fallback: try application/zip then image/png when application/octet-stream is rejected (415/400) - Accept 200 OK JSON blob descriptor: parse response and use 'url' field when present (blossom.primal.net, nostr.media return JSON not blossom://) - Add nip98 and base64 deps for auth; add bitcoin_hashes for payload hash Co-authored-by: Cursor <cursoragent@cursor.com>
…muser - Derive shared key from (trade_keys, identity_keys) and fetch/unwrap gift wraps so DMs sent to the user's identity appear in getdmuser. - Also derive (trade_keys, mostro_pubkey) and fetch so admin replies from the send_admin_dm_attach flow are shown. - Reuse derive_shared_key_bytes from util/messaging (same ECDH as send_admin_dm_attach). No longer use get_all_trade_and_counterparty_keys; counterparty_pubkey is not used in this setup.
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (7)
WalkthroughAdds documentation, a new SendAdminDmAttach CLI command (encrypts and uploads attachments to Blossom, sends via shared-key DM), refactors DM retrieval to use per-order shared keys, extends messaging utilities for shared-key gift-wraps, updates deps for crypto, and introduces DB and misc helpers. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant CLI as CLI Handler
participant DB as Database
participant Crypto as Crypto (ECDH / ChaCha20-Poly1305)
participant Blossom as Blossom Server
participant Messaging as Messaging Utils
participant Relay as Nostr Relay
User->>CLI: sendadmindmattach --pubkey --order-id --file
CLI->>CLI: Validate file size & metadata
CLI->>DB: Load order by order_id -> trade_keys
DB-->>CLI: Return order with trade_keys
CLI->>Crypto: Derive shared_key(trade_keys, admin_pubkey)
Crypto-->>CLI: shared_key
CLI->>Crypto: Encrypt file -> encrypted_blob, nonce
CLI->>Blossom: Upload encrypted_blob (signed PUT)
Blossom-->>CLI: blossom_url
CLI->>Messaging: Build attachment payload (url, nonce, mime, sizes)
CLI->>Messaging: Send payload via shared-key NIP-59 gift-wrap
Messaging->>Relay: Publish wrapped event
Relay-->>Messaging: Ack
Messaging-->>CLI: Success
CLI-->>User: Display summary / success
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~70 minutes Possibly related PRs
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/util/misc.rs (1)
13-23:⚠️ Potential issue | 🟠 MajorAdd one-time migration from legacy data directory.
Switching the home subpath without fallback can strand existing
mcli.dbdata for current users.Proposed compatibility fix
pub fn get_mcli_path() -> String { let home_dir = dirs::home_dir().expect("Couldn't get home directory"); let mcli_path = format!("{}/.mcli", home_dir.display()); + let legacy_mcli_path = format!("{}/.mcliUserA", home_dir.display()); + + if !Path::new(&mcli_path).exists() && Path::new(&legacy_mcli_path).exists() { + fs::rename(&legacy_mcli_path, &mcli_path) + .unwrap_or_else(|e| panic!("Couldn't migrate legacy mcli directory: {}", e)); + } + if !Path::new(&mcli_path).exists() { match fs::create_dir(&mcli_path) { Ok(_) => println!("Directory {} created.", mcli_path),
🧹 Nitpick comments (5)
.cursor/commands/pull_request.md (1)
26-31: Consider making the PR template more project-specific.The current template uses generic placeholders. Consider aligning with the project's actual CI checks:
📝 Suggested template improvement
## PR Template -- [ ] Feature A -- [ ] Bug fix B -- [ ] Unit tests pass -- [ ] Manual testing completed +- [ ] Changes description +- [ ] `cargo fmt --all` passes +- [ ] `cargo clippy --all-targets --all-features` passes +- [ ] `cargo test` passes +- [ ] Manual testing completed +- [ ] Documentation updated (if applicable)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.cursor/commands/pull_request.md around lines 26 - 31, Update the generic "## PR Template" checklist in .cursor/commands/pull_request.md to use project-specific checks: replace the placeholders "- [ ] Feature A", "- [ ] Bug fix B", "- [ ] Unit tests pass", "- [ ] Manual testing completed" with concrete items like referencing the related issue/PR number, CI status (unit/integration/e2e), lint/format checks, changelog or release notes entry, migration steps if any, and required approvers/reviewers; ensure the new checklist names are explicit and reflect the repository's actual CI and release processes so contributors follow the correct verification steps.src/cli/send_admin_dm_attach.rs (1)
74-82: Consider adding a timeout to HTTP requests.The
reqwest::Client::new()uses default settings without an explicit timeout. If a Blossom server becomes unresponsive, this could cause the CLI to hang indefinitely.Suggested fix
async fn upload_to_blossom(trade_keys: &Keys, encrypted_blob: Vec<u8>) -> Result<String> { use reqwest::StatusCode; + use std::time::Duration; - let client = reqwest::Client::new(); + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(60)) + .build() + .map_err(|e| anyhow::anyhow!("failed to create HTTP client: {e}"))?; let payload_hash = Sha256Hash::hash(&encrypted_blob);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cli/send_admin_dm_attach.rs` around lines 74 - 82, The HTTP client in upload_to_blossom currently uses reqwest::Client::new() with no timeout; change it to construct the client via reqwest::Client::builder() and set a sensible timeout (e.g., Duration::from_secs(10) or 30) so requests cannot hang indefinitely, and ensure you import std::time::Duration; update uses of the client variable accordingly in upload_to_blossom to use the new client instance.docs/send_admin_dm_attach_flow.md (1)
30-44: Remove local filesystem paths from documentation.The code block contains absolute paths to a developer's local machine (
/home/pinballwizard/rust_prj/mostro_p2p/mostro-cli/...). These should be removed or converted to relative paths for cleaner documentation.Suggested fix example
-```198:335:/home/pinballwizard/rust_prj/mostro_p2p/mostro-cli/src/cli/send_admin_dm_attach.rs +```rust pub async fn execute_send_admin_dm_attach(This pattern appears throughout the document (lines 54-57, 68-77, 89-105, 118-151, 167-172, 186-296). Consider cleaning up all instances.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/send_admin_dm_attach_flow.md` around lines 30 - 44, Remove all absolute local filesystem prefixes from code fences in the documentation so they don't leak developer machine paths; for each code block (e.g., the block starting with "pub async fn execute_send_admin_dm_attach" and other occurrences listed in the comment) strip the leading "/home/..." portion and present either the raw code fence (```rust) or a relative path if a file reference is needed, ensuring symbols like execute_send_admin_dm_attach remain intact and no absolute paths remain in the document.src/util/messaging.rs (2)
166-175: Fixed fetch bounds can silently truncate shared-key chat history.Using a hardcoded 7-day window with
limit(100)can drop messages in active disputes with no indication to callers. Consider caller-configurable bounds and/or pagination.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/util/messaging.rs` around lines 166 - 175, The hardcoded 7-day window (seven_days_secs/wide_since) and fixed .limit(100) in the Filter built for shared_keys.public_key() can silently truncate active chat history; change the API around the function that constructs this Filter (the use of Timestamp::now(), seven_days_secs, wide_since, Filter::new() with .since(...) and .limit(...)) to accept caller-configurable time range and page size parameters and implement pagination (looped/continuation fetches) when the result count equals the page limit so callers can request older events, or alternatively expose these bounds via configuration defaults instead of the fixed 7-day/100 limit.
31-55: Return explicit errors from shared-key helper APIs instead of collapsing toNone.These helpers currently hide root causes (missing keys, parse errors, ECDH failures), which makes CLI failures harder to debug. Prefer
Resultwith context.♻️ Proposed refactor
-pub fn derive_shared_keys( - admin_keys: Option<&Keys>, - counterparty_pubkey: Option<&PublicKey>, -) -> Option<Keys> { - let admin = admin_keys?; - let cp_pk = counterparty_pubkey?; - let shared_bytes = nostr_sdk::util::generate_shared_key(admin.secret_key(), cp_pk).ok()?; - let sk = nostr_sdk::SecretKey::from_slice(&shared_bytes).ok()?; - Some(Keys::new(sk)) +pub fn derive_shared_keys( + admin_keys: Option<&Keys>, + counterparty_pubkey: Option<&PublicKey>, +) -> Result<Keys> { + let admin = admin_keys.ok_or_else(|| anyhow::anyhow!("missing local keys"))?; + let cp_pk = counterparty_pubkey.ok_or_else(|| anyhow::anyhow!("missing counterparty pubkey"))?; + let shared_bytes = nostr_sdk::util::generate_shared_key(admin.secret_key(), cp_pk) + .map_err(|e| anyhow::anyhow!("failed to derive shared key bytes: {e}"))?; + let sk = nostr_sdk::SecretKey::from_slice(&shared_bytes) + .map_err(|e| anyhow::anyhow!("invalid shared key scalar: {e}"))?; + Ok(Keys::new(sk)) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/util/messaging.rs` around lines 31 - 55, The three helpers (derive_shared_keys, derive_shared_key_hex, keys_from_shared_hex) should return Result types with contextual errors instead of Option so callers can see root causes: change derive_shared_keys to return Result<Keys, YourError> and explicitly check for missing admin_keys and missing counterparty_pubkey returning distinct errors (e.g., MissingAdminKey, MissingCounterpartyPubkey), propagate nostr_sdk::util::generate_shared_key errors as EcdhError, map SecretKey::from_slice failure to SecretKeyParseError, change derive_shared_key_hex to parse the counterparty string with PublicKey::parse and return ParsePublicKeyError on failure before calling derive_shared_keys, and change keys_from_shared_hex to return Result<Keys, YourError> mapping nostr_sdk::Keys::parse errors to ParseSharedHexError; implement or use a small error enum (or anyhow with context) and replace .ok()? with proper ? and map_err calls in each referenced function name.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Cargo.toml`:
- Around line 50-54: Update the outdated cryptographic crates in Cargo.toml by
bumping rand_core from 0.6.4 to the latest stable (e.g., 0.10.0) and bitcoin
from 0.32.2 to the latest stable (e.g., 0.32.8) while keeping other pins
(chacha20poly1305, bitcoin_hashes, base64) unchanged; after updating the
rand_core and bitcoin versions, run cargo update and add a CI step to run cargo
audit to validate the full dependency tree for RustSec advisories and ensure no
breaking changes affect code referencing those crates (search for uses of
rand_core and bitcoin types/functions to fix any API changes).
In `@docs/commands.md`:
- Around line 171-181: Update the docs for admcancel and admsettle so the flags
and description use "dispute" rather than "order": change the arg from
`--order-id <UUID>` to `--dispute-id <UUID>` and adjust the Description lines to
say "Cancel a dispute" and "Settle a dispute" respectively; ensure the Handler
references remain `execute_admin_cancel_dispute(order_id, ctx)` and
`execute_admin_settle_dispute(order_id, ctx)` so readers know these handlers
operate on dispute IDs.
In `@docs/senddm_flow.md`:
- Around line 33-45: Remove machine-local absolute paths from all code
references in the docs (e.g., the Rust fences that currently start with paths
like "12:69:/home/pinballwizard/.../send_dm.rs" and similar entries for
util/messaging.rs); replace them with plain Rust code fences (```rust) or
repo-relative references and keep the function/method names (e.g.,
execute_send_dm in send_dm.rs and functions in util/messaging.rs) so readers can
locate the code without machine-specific paths. Ensure every occurrence listed
(including ranges 59-69, 78-91, 100-111, 125-179, 190-199, 209-248, 274-276) is
updated consistently.
In `@src/cli/dm_to_user.rs`:
- Around line 45-51: The success message is inconsistent with the new shared-key
flow: update the call to print_success_message to reflect the shared-key custom
wrap instead of "Gift wrap message sent successfully!"; locate the block where
print_info_line("💡", "Sending shared-key custom wrap message...") and
send_admin_chat_message_via_shared_key(...) are used and change the final
print_success_message invocation to a matching message such as "Shared-key
custom wrap message sent successfully!" so the messaging aligns with
send_admin_chat_message_via_shared_key.
In `@src/cli/get_dm_user.rs`:
- Line 76: Replace the println! call that currently prints an empty string
literal by calling println!() with no arguments; locate the println!("")
invocation in src/cli/get_dm_user.rs (the empty-line print) and remove the ""
argument so the macro is invoked as println!() to satisfy Clippy.
- Line 64: The retain closure in messages.retain currently does an unnecessary
cast (*ts as i64); remove the redundant cast and compare the existing i64 value
directly against cutoff_ts (e.g., change the closure used by messages.retain to
use *ts or ts instead of (*ts as i64)) so the comparison is between two i64
values and clippy CI will be satisfied; keep the same variables (messages, ts,
cutoff_ts) and only remove the `as i64` cast.
- Line 80: The clippy warning flags an unnecessary cast in the DateTime
creation; in the invocation of chrono::DateTime::from_timestamp inside
get_dm_user.rs (the line using from_timestamp(*ts as i64, 0)), remove the
redundant "as i64" cast and pass *ts directly (matching the earlier similar fix
at line 64) so the call becomes from_timestamp(*ts, 0).
In `@src/util/messaging.rs`:
- Around line 103-106: Replace the silent fallback for POW so invalid values
surface: instead of unwrapping var("POW") and using .parse().unwrap_or(0), parse
the env value and return or propagate a proper error (or log and exit) when
parsing fails; locate the POW handling around the let pow: u8 = var("POW") ...
expression in messaging.rs and change it to explicitly handle
VarError/ParseIntError (e.g., map_err with a descriptive error message or use
expect with a clear message) so malformed POW values are not silently coerced to
0.
---
Nitpick comments:
In @.cursor/commands/pull_request.md:
- Around line 26-31: Update the generic "## PR Template" checklist in
.cursor/commands/pull_request.md to use project-specific checks: replace the
placeholders "- [ ] Feature A", "- [ ] Bug fix B", "- [ ] Unit tests pass", "- [
] Manual testing completed" with concrete items like referencing the related
issue/PR number, CI status (unit/integration/e2e), lint/format checks, changelog
or release notes entry, migration steps if any, and required
approvers/reviewers; ensure the new checklist names are explicit and reflect the
repository's actual CI and release processes so contributors follow the correct
verification steps.
In `@docs/send_admin_dm_attach_flow.md`:
- Around line 30-44: Remove all absolute local filesystem prefixes from code
fences in the documentation so they don't leak developer machine paths; for each
code block (e.g., the block starting with "pub async fn
execute_send_admin_dm_attach" and other occurrences listed in the comment) strip
the leading "/home/..." portion and present either the raw code fence (```rust)
or a relative path if a file reference is needed, ensuring symbols like
execute_send_admin_dm_attach remain intact and no absolute paths remain in the
document.
In `@src/cli/send_admin_dm_attach.rs`:
- Around line 74-82: The HTTP client in upload_to_blossom currently uses
reqwest::Client::new() with no timeout; change it to construct the client via
reqwest::Client::builder() and set a sensible timeout (e.g.,
Duration::from_secs(10) or 30) so requests cannot hang indefinitely, and ensure
you import std::time::Duration; update uses of the client variable accordingly
in upload_to_blossom to use the new client instance.
In `@src/util/messaging.rs`:
- Around line 166-175: The hardcoded 7-day window (seven_days_secs/wide_since)
and fixed .limit(100) in the Filter built for shared_keys.public_key() can
silently truncate active chat history; change the API around the function that
constructs this Filter (the use of Timestamp::now(), seven_days_secs,
wide_since, Filter::new() with .since(...) and .limit(...)) to accept
caller-configurable time range and page size parameters and implement pagination
(looped/continuation fetches) when the result count equals the page limit so
callers can request older events, or alternatively expose these bounds via
configuration defaults instead of the fixed 7-day/100 limit.
- Around line 31-55: The three helpers (derive_shared_keys,
derive_shared_key_hex, keys_from_shared_hex) should return Result types with
contextual errors instead of Option so callers can see root causes: change
derive_shared_keys to return Result<Keys, YourError> and explicitly check for
missing admin_keys and missing counterparty_pubkey returning distinct errors
(e.g., MissingAdminKey, MissingCounterpartyPubkey), propagate
nostr_sdk::util::generate_shared_key errors as EcdhError, map
SecretKey::from_slice failure to SecretKeyParseError, change
derive_shared_key_hex to parse the counterparty string with PublicKey::parse and
return ParsePublicKeyError on failure before calling derive_shared_keys, and
change keys_from_shared_hex to return Result<Keys, YourError> mapping
nostr_sdk::Keys::parse errors to ParseSharedHexError; implement or use a small
error enum (or anyhow with context) and replace .ok()? with proper ? and map_err
calls in each referenced function name.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (25)
.cursor/commands/build.md.cursor/commands/pull_request.md.cursor/commands/update_docs.mdCargo.tomldocs/architecture.mddocs/commands.mddocs/database.mddocs/overview.mddocs/send_admin_dm_attach_flow.mddocs/senddm_flow.mdsrc/cli.rssrc/cli/add_invoice.rssrc/cli/dm_to_user.rssrc/cli/get_dm.rssrc/cli/get_dm_user.rssrc/cli/last_trade_index.rssrc/cli/list_orders.rssrc/cli/new_order.rssrc/cli/rate_user.rssrc/cli/send_admin_dm_attach.rssrc/cli/take_dispute.rssrc/db.rssrc/util/messaging.rssrc/util/misc.rssrc/util/mod.rs
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/cli/get_dm_user.rs (1)
67-89: Sender label branch is dead after pre-filtering bysender_pk == pubkey.After
messages.retain(|(_, _, sender_pk)| *sender_pk == pubkey), theelse("🧑 You") branch cannot execute. Simplify the label logic, or remove the retain if full conversation output is intended.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cli/get_dm_user.rs` around lines 67 - 89, The sender label branch is dead because messages are pre-filtered with messages.retain(|(_, _, sender_pk)| *sender_pk == pubkey), so in the loop for (idx, (content, ts, sender_pk)) the else branch (🧑 You) will never run; fix by either removing the retain to keep both parties in messages (so the existing from_label logic in the loop remains valid) or simplify the loop label to always render the counterparty label (e.g., replace the conditional in the from_label computation to a single counterparty label) — modify the code around messages.retain and the from_label calculation to choose one of those two approaches and remove the unreachable branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/cli/get_dm_user.rs`:
- Around line 56-65: The command allows arbitrary minute lookback but
fetch_gift_wraps_for_shared_key only returns the last 7 days, so add an explicit
guard that validates the requested since value (minutes) before or immediately
after calling fetch_gift_wraps_for_shared_key and return an informative error or
warning when the requested window exceeds the helper's 7-day window; for
example, compute max_minutes = 7 * 24 * 60 and if *since > max_minutes, return
an Err (or print and exit) explaining that lookback is limited to 7 days,
referencing the fetch_gift_wraps_for_shared_key call and the messages variable
so reviewers can find where to apply the check.
- Around line 60-63: The cutoff timestamp computation using
chrono::Utc::now().checked_sub_signed(...).unwrap() in src/cli/get_dm_user.rs
can panic for extreme user-provided `since` values; replace the unwrap with
explicit error handling in the function that computes/uses cutoff_ts (e.g.,
return a user-facing error or exit with a clear message) when checked_sub_signed
returns None, and validate/clamp `since` if appropriate before calling
checked_sub_signed. Also remove the unreachable else branch in the
sender-labeling logic (the branch that runs when messages are not from the
counterparty) because earlier filtering to the counterparty already guarantees
only counterparty messages; simplify the label code in the function that assigns
sender labels to only the expected branch.
---
Nitpick comments:
In `@src/cli/get_dm_user.rs`:
- Around line 67-89: The sender label branch is dead because messages are
pre-filtered with messages.retain(|(_, _, sender_pk)| *sender_pk == pubkey), so
in the loop for (idx, (content, ts, sender_pk)) the else branch (🧑 You) will
never run; fix by either removing the retain to keep both parties in messages
(so the existing from_label logic in the loop remains valid) or simplify the
loop label to always render the counterparty label (e.g., replace the
conditional in the from_label computation to a single counterparty label) —
modify the code around messages.retain and the from_label calculation to choose
one of those two approaches and remove the unreachable branch.
Summary
Add shared-key DM attachment flow and improve DM handling/documentation in
mostro-cli.Details
New admin DM attachment command
send_admin_dm_attachCLI command andsrc/cli/send_admin_dm_attach.rs.src/util/messaging.rs(deriving shared keys, building/wrapping/unwrapping gift wraps, fetching gift wrap events).DM receive path improvements
get_dm_userto:(trade_keys, identity_keys)so user‑targeted shared-key messages are visible.(trade_keys, mostro_pubkey)so admin replies from the attachment flow are also shown.CLI and utility wiring
src/cli.rsand re‑export helpers fromsrc/util/mod.rs.dm_to_user,get_dm,add_invoice,last_trade_index,list_orders,new_order,rate_user,take_dispute) to use the new messaging helpers where appropriate.src/db.rsto retrieve trade keys used by the shared-key flows.Documentation and DX
docs/overview.md,docs/architecture.md,docs/commands.md,docs/database.md.docs/send_admin_dm_attach_flow.md,docs/senddm_flow.md..cursor/commands/{build,update_docs,pull_request}.mdfor contributor workflows.Cargo.tomldependencies and updateCargo.lockaccordingly.Testing
cargo build.getdmuserto confirm both classic and shared-key DMs are returned.dm_to_userandsend_admin_dm_attachto verify shared-key send/receive paths interoperate correctly.Summary by CodeRabbit
New Features
Documentation
Dependencies