Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5b090d9
feat: improving parsing functions
arkanoider Sep 28, 2025
5ad663a
chore: going on to uniform message fetching calls
arkanoider Sep 29, 2025
fbe1c9a
chore: almost remove wait for dm function with fetch event
arkanoider Sep 29, 2025
ebe1bfc
chore: almost remove wait for dm function with fetch event
arkanoider Sep 29, 2025
55c6d3f
chore: testing all patterns with new code
arkanoider Sep 30, 2025
4fcb3bf
fix: some fixes on message management
arkanoider Oct 5, 2025
29baccb
fix: added a check to avoid UNIQUE constraint error - now if an order…
arkanoider Oct 7, 2025
ac3f69b
feat: add invalid fiat currency managed
arkanoider Oct 7, 2025
60e7638
chore: rabbit fixes
arkanoider Oct 7, 2025
47ff00e
chore: rabbit fixes
arkanoider Oct 7, 2025
d9d356a
feat: added correct since time calculation for get dm command - impro…
arkanoider Oct 8, 2025
807ce79
chore: added trade index number printout in new order and take order …
arkanoider Oct 8, 2025
08ea97e
chore: rabbit improvement
arkanoider Oct 8, 2025
07e4165
chore: rabbit improvement
arkanoider Oct 8, 2025
c55069e
chore: fix on rabbit improvements
arkanoider Oct 8, 2025
275facf
fix: moved some logic parts in wait dm function to avoid issues with …
arkanoider Oct 9, 2025
61636b1
feat: first testable mostro cli version with last_trade_index command…
arkanoider Oct 9, 2025
39f4b26
feat: improved reception logic and small fix for last trade index
arkanoider Oct 9, 2025
51f8588
chore: updateded README.md
arkanoider Oct 9, 2025
6301df0
chore: fix by rabbit
arkanoider Oct 9, 2025
6bc8f73
feat: fixed some wrong key in decryption
arkanoider Oct 9, 2025
a3be582
chore: fix for clippy gh actions
arkanoider Oct 9, 2025
4edef4d
chore: fix in README.md
arkanoider Oct 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ uuid = { version = "1.18.1", features = [
dotenvy = "0.15.6"
lightning-invoice = { version = "0.33.2", features = ["std"] }
reqwest = { version = "0.12.23", features = ["json"] }
mostro-core = "0.6.50"
mostro-core = "0.6.55"
lnurl-rs = "0.9.0"
pretty_env_logger = "0.5.0"
openssl = { version = "0.10.73", features = ["vendored"] }
Expand Down
4 changes: 2 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,11 +489,11 @@ impl Commands {

// DM retrieval commands
Commands::GetDm { since, from_user } => {
execute_get_dm(Some(since), false, from_user, ctx).await
execute_get_dm(since, false, from_user, ctx).await
}
Commands::GetDmUser { since } => execute_get_dm_user(since, ctx).await,
Commands::GetAdminDm { since, from_user } => {
execute_get_dm(Some(since), true, from_user, ctx).await
execute_get_dm(since, true, from_user, ctx).await
}
Comment on lines 497 to 504
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Oct 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix borrow when calling execute_get_dm

execute_get_dm now takes &i64. Passing since by value won’t compile (expected &i64, found i64). Borrow it at both call sites.

-                execute_get_dm(since, false, from_user, ctx).await
+                execute_get_dm(&since, false, from_user, ctx).await-                execute_get_dm(since, true, from_user, ctx).await
+                execute_get_dm(&since, true, from_user, ctx).await
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// DM retrieval commands
Commands::GetDm { since, from_user } => {
execute_get_dm(Some(since), false, from_user, ctx).await
execute_get_dm(since, false, from_user, ctx).await
}
Commands::GetDmUser { since } => execute_get_dm_user(since, ctx).await,
Commands::GetAdminDm { since, from_user } => {
execute_get_dm(Some(since), true, from_user, ctx).await
execute_get_dm(since, true, from_user, ctx).await
}
// DM retrieval commands
Commands::GetDm { since, from_user } => {
execute_get_dm(&since, false, from_user, ctx).await
}
Commands::GetDmUser { since } => execute_get_dm_user(since, ctx).await,
Commands::GetAdminDm { since, from_user } => {
execute_get_dm(&since, true, from_user, ctx).await
}
🤖 Prompt for AI Agents
In src/cli.rs around lines 490 to 497, the calls to execute_get_dm pass since by
value but the function now expects &i64; change the two call sites to pass a
reference (e.g. &since) instead of since so the types match (update both the
Commands::GetDm and Commands::GetAdminDm invocations).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since is passed correctly also the:
cargo clippy --all-targets --all-features -- -D warnings

command is happy!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the actual signature of execute_get_dm function
echo "=== Checking execute_get_dm signature ==="
rg -n "pub async fn execute_get_dm" src/cli/get_dm.rs -A 3

echo ""
echo "=== Checking call sites in src/cli.rs ==="
rg -n "execute_get_dm\(" src/cli.rs -B 1 -A 1

Length of output: 482


@arkanoider, thank you for confirming! Just to be clear: the diff shown above passes since by value (without the &), but execute_get_dm expects &i64. If you've already added the borrow operator (&since) in your local code and clippy is happy, that's perfect! ✅

If you haven't applied the fix yet, you'll need to change both call sites to:

  • execute_get_dm(&since, false, from_user, ctx).await
  • execute_get_dm(&since, true, from_user, ctx).await

Once that's done, the types will align correctly and clippy will pass.

🐰


🧬 Code Graph Analysis Results

[src/cli/adm_send_dm.rs:6-17]

pub async fn execute_adm_send_dm(receiver: PublicKey, ctx: &Context, message: &str) -> Result<()> {
    println!(
        "SENDING DM with admin keys: {}",
        ctx.context_keys.public_key().to_hex()
    );

    send_admin_gift_wrap_dm(&ctx.client, &ctx.context_keys, &receiver, message).await?;

    println!("Admin gift wrap message sent to {}", receiver);

    Ok(())
}

[src/cli/conversation_key.rs:5-17]

pub async fn execute_conversation_key(trade_keys: &Keys, receiver: PublicKey) -> Result<()> {
    // Derive conversation key
    let ck = ConversationKey::derive(trade_keys.secret_key(), &receiver)?;
    let key = ck.as_bytes();
    let mut ck_hex = vec![];
    for i in key {
        ck_hex.push(format!("{:02x}", i));
    }
    let ck_hex = ck_hex.join("");
    println!("Conversation key: {:?}", ck_hex);

    Ok(())
}

[src/cli/list_disputes.rs:7-21]

pub async fn execute_list_disputes(ctx: &Context) -> Result<()> {
    // Print mostro pubkey
    println!(
        "Requesting disputes from mostro pubId - {}",
        &ctx.mostro_pubkey
    );

    // Get orders from relays
    let table_of_disputes =
        fetch_events_list(ListKind::Disputes, None, None, None, ctx, None).await?;
    let table = print_disputes_table(table_of_disputes)?;
    println!("{table}");

    Ok(())
}

[src/cli/restore.rs:7-32]

pub async fn execute_restore(
    identity_keys: &Keys,
    mostro_key: PublicKey,
    client: &Client,
) -> Result<()> {
    let restore_message = Message::new_restore(None);
    let message_json = restore_message
        .as_json()
        .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    // Send the restore message to Mostro server
    send_dm(
        client,
        Some(identity_keys),
        identity_keys,
        &mostro_key,
        message_json,
        None,
        false,
    )
    .await?;

    println!("Restore message sent successfully. Recovering pending orders and disputes...");

    Ok(())
}

[src/cli/dm_to_user.rs:7-33]

pub async fn execute_dm_to_user(
    receiver: PublicKey,
    client: &Client,
    order_id: &Uuid,
    message: &str,
    pool: &SqlitePool,
) -> Result<()> {
    // Get the order
    let order = Order::get_by_id(pool, &order_id.to_string())
        .await
        .map_err(|_| anyhow::anyhow!("order {} not found", order_id))?;
    // Get the trade keys
    let trade_keys = match order.trade_keys.as_ref() {
        Some(trade_keys) => Keys::parse(trade_keys)?,
        None => anyhow::bail!("No trade_keys found for this order"),
    };

    // Send the DM
    println!(
        "SENDING DM with trade keys: {}",
        trade_keys.public_key().to_hex()
    );

    send_gift_wrap_dm(client, &trade_keys, &receiver, message).await?;

    Ok(())
}

[src/cli/get_dm.rs:10-38]

pub async fn execute_get_dm(
    since: &i64,
    admin: bool,
    from_user: &bool,
    ctx: &Context,
) -> Result<()> {
    // Get the list kind
    let list_kind = match (admin, from_user) {
        (true, true) => ListKind::PrivateDirectMessagesUser,
        (true, false) => ListKind::DirectMessagesAdmin,
        (false, true) => ListKind::PrivateDirectMessagesUser,
        (false, false) => ListKind::DirectMessagesUser,
    };

    // Fetch the requested events
    let all_fetched_events =
        fetch_events_list(list_kind, None, None, None, ctx, Some(since)).await?;

    // Extract (Message, u64) tuples from Event::MessageTuple variants
    let mut dm_events: Vec<(Message, u64)> = Vec::new();
    for event in all_fetched_events {
        if let Event::MessageTuple(tuple) = event {
            dm_events.push(*tuple);
        }
    }

    print_direct_messages(&dm_events, &ctx.pool).await?;
    Ok(())
}

[src/cli/send_dm.rs:8-47]

pub async fn execute_send_dm(
    receiver: PublicKey,
    ctx: &Context,
    order_id: &Uuid,
    message: &str,
) -> Result<()> {
    let message = Message::new_dm(
        None,
        None,
        Action::SendDm,
        Some(Payload::TextMessage(message.to_string())),
    )
    .as_json()
    .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    let trade_keys =
        if let Ok(order_to_vote) = Order::get_by_id(&ctx.pool, &order_id.to_string()).await {
            match order_to_vote.trade_keys.as_ref() {
                Some(trade_keys) => Keys::parse(trade_keys)?,
                None => {
                    anyhow::bail!("No trade_keys found for this order");
                }
            }
        } else {
            return Err(anyhow::anyhow!("order {} not found", order_id));
        };

    send_dm(
        &ctx.client,
        None,
        &trade_keys,
        &receiver,
        message,
        None,
        false,
    )
    .await?;

    Ok(())
}

[src/cli/rate_user.rs:19-59]

pub async fn execute_rate_user(order_id: &Uuid, rating: &u8, ctx: &Context) -> Result<()> {
    // Check boundaries
    let rating_content = get_user_rate(rating)?;

    // Get the trade keys
    let trade_keys =
        if let Ok(order_to_vote) = Order::get_by_id(&ctx.pool, &order_id.to_string()).await {
            match order_to_vote.trade_keys.as_ref() {
                Some(trade_keys) => Keys::parse(trade_keys)?,
                None => {
                    return Err(anyhow::anyhow!("No trade_keys found for this order"));
                }
            }
        } else {
            return Err(anyhow::anyhow!("order {} not found", order_id));
        };

    // Create rating message of counterpart
    let rate_message = Message::new_order(
        Some(*order_id),
        None,
        None,
        Action::RateUser,
        Some(rating_content),
    )
    .as_json()
    .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    send_dm(
        &ctx.client,
        Some(&ctx.identity_keys),
        &trade_keys,
        &ctx.mostro_pubkey,
        rate_message,
        None,
        false,
    )
    .await?;

    Ok(())
}

[src/cli/list_orders.rs:9-72]

pub async fn execute_list_orders(
    kind: &Option<String>,
    currency: &Option<String>,
    status: &Option<String>,
    ctx: &Context,
) -> Result<()> {
    // Used to get upper currency string to check against a list of tickers
    let mut upper_currency: Option<String> = None;
    // Default status is pending
    let mut status_checked: Option<Status> = Some(Status::Pending);
    // Default kind is none
    let mut kind_checked: Option<mostro_core::order::Kind> = None;

    // New check against strings
    if let Some(s) = status {
        status_checked = Some(
            Status::from_str(s)
                .map_err(|e| anyhow::anyhow!("Not valid status '{}': {:?}", s, e))?,
        );
    }

    // Print status requested
    if let Some(status) = &status_checked {
        println!("You are searching orders with status {:?}", status);
    }
    // New check against strings for kind
    if let Some(k) = kind {
        kind_checked = Some(
            mostro_core::order::Kind::from_str(k)
                .map_err(|e| anyhow::anyhow!("Not valid order kind '{}': {:?}", k, e))?,
        );
        if let Some(kind) = &kind_checked {
            println!("You are searching {} orders", kind);
        }
    }

    // Uppercase currency
    if let Some(curr) = currency {
        upper_currency = Some(curr.to_uppercase());
        if let Some(currency) = &upper_currency {
            println!("You are searching orders with currency {}", currency);
        }
    }

    println!(
        "Requesting orders from mostro pubId - {}",
        &ctx.mostro_pubkey
    );

    // Get orders from relays
    let table_of_orders = fetch_events_list(
        ListKind::Orders,
        status_checked,
        upper_currency,
        kind_checked,
        ctx,
        None,
    )
    .await?;
    let table = print_orders_table(table_of_orders)?;
    println!("{table}");

    Ok(())
}

[src/cli/take_order.rs:54-124]

pub async fn execute_take_order(
    order_id: &Uuid,
    action: Action,
    invoice: &Option<String>,
    amount: Option<u32>,
    ctx: &Context,
) -> Result<()> {
    let action_name = match action {
        Action::TakeBuy => "take buy",
        Action::TakeSell => "take sell",
        _ => return Err(anyhow::anyhow!("Invalid action for take order")),
    };

    println!(
        "Request of {} order {} from mostro pubId {}",
        action_name, order_id, ctx.mostro_pubkey
    );

    // Create payload based on action type
    let payload = create_take_order_payload(action.clone(), invoice, amount)?;

    // Create request id
    let request_id = Uuid::new_v4().as_u128() as u64;

    // Create message
    let take_order_message = Message::new_order(
        Some(*order_id),
        Some(request_id),
        Some(ctx.trade_index),
        action.clone(),
        payload,
    );

    // Send dm to receiver pubkey
    println!(
        "SENDING DM with trade keys: {:?}",
        ctx.trade_keys.public_key().to_hex()
    );

    let message_json = take_order_message
        .as_json()
        .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    // Send the DM
    // This is so we can wait for the gift wrap event in the main thread
    let _ = send_dm(
        &ctx.client,
        Some(&ctx.identity_keys),
        &ctx.trade_keys,
        &ctx.mostro_pubkey,
        message_json,
        None,
        false,
    )
    .await;

    // Wait for the DM to be sent from mostro
    let recv_event = wait_for_dm(ctx, None).await?;

    // Parse the incoming DM
    let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await;
    if let Some(message) = messages.first() {
        let message = message.0.get_inner_message_kind();
        if message.request_id == Some(request_id) {
            if let Err(e) = print_commands_results(message, None, ctx).await {
                println!("Error in print_commands_results: {}", e);
            }
        }
    }
    Ok(())
}

[src/cli/add_invoice.rs:12-84]

pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) -> Result<()> {
    let order = Order::get_by_id(&ctx.pool, &order_id.to_string()).await?;
    let trade_keys = order
        .trade_keys
        .clone()
        .ok_or(anyhow::anyhow!("Missing trade keys"))?;
    let order_trade_keys = Keys::parse(&trade_keys)?;
    println!(
        "Order trade keys: {:?}",
        order_trade_keys.public_key().to_hex()
    );

    println!(
        "Sending a lightning invoice for order {} to mostro pubId {}",
        order_id, ctx.mostro_pubkey
    );
    // Check invoice string
    let ln_addr = LightningAddress::from_str(invoice);
    let payload = if ln_addr.is_ok() {
        Some(Payload::PaymentRequest(None, invoice.to_string(), None))
    } else {
        match is_valid_invoice(invoice) {
            Ok(i) => Some(Payload::PaymentRequest(None, i.to_string(), None)),
            Err(e) => {
                return Err(anyhow::anyhow!("Invalid invoice: {}", e));
            }
        }
    };

    // Create request id
    let request_id = Uuid::new_v4().as_u128() as u64;
    // Create AddInvoice message
    let add_invoice_message = Message::new_order(
        Some(*order_id),
        Some(request_id),
        None,
        Action::AddInvoice,
        payload,
    );

    // Serialize the message
    let message_json = add_invoice_message
        .as_json()
        .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    // Send the DM
    let _ = send_dm(
        &ctx.client,
        Some(&ctx.identity_keys),
        &order_trade_keys,
        &ctx.mostro_pubkey,
        message_json,
        None,
        false,
    )
    .await;

    // Wait for the DM to be sent from mostro
    let recv_event = wait_for_dm(ctx, Some(&order_trade_keys)).await?;

    // Parse the incoming DM
    let messages = parse_dm_events(recv_event, &order_trade_keys, None).await;
    if let Some(message) = messages.first() {
        let message = message.0.get_inner_message_kind();
        if message.request_id == Some(request_id) {
            if let Err(e) = print_commands_results(message, Some(order.clone()), ctx).await {
                println!("Error in print_commands_results: {}", e);
            }
        }
    }

    Ok(())
}

[src/cli/take_dispute.rs:64-85]

pub async fn execute_take_dispute(dispute_id: &Uuid, ctx: &Context) -> Result<()> {
    println!(
        "Request of take dispute {} from mostro pubId {}",
        dispute_id,
        ctx.mostro_pubkey.clone()
    );
    // Create takebuy message
    let take_dispute_message = Message::new_dispute(
        Some(*dispute_id),
        None,
        None,
        Action::AdminTakeDispute,
        None,
    )
    .as_json()
    .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    println!("Admin keys: {:?}", ctx.context_keys.public_key.to_string());

    admin_send_dm(ctx, take_dispute_message).await?;
    Ok(())
}

[src/cli/take_dispute.rs:7-26]

pub async fn execute_admin_add_solver(npubkey: &str, ctx: &Context) -> Result<()> {
    println!(
        "Request of add solver with pubkey {} from mostro pubId {}",
        npubkey, &ctx.mostro_pubkey
    );
    // Create takebuy message
    let take_dispute_message = Message::new_dispute(
        Some(Uuid::new_v4()),
        None,
        None,
        Action::AdminAddSolver,
        Some(Payload::TextMessage(npubkey.to_string())),
    )
    .as_json()
    .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    admin_send_dm(ctx, take_dispute_message).await?;

    Ok(())
}

[src/cli/take_dispute.rs:47-62]

pub async fn execute_admin_settle_dispute(dispute_id: &Uuid, ctx: &Context) -> Result<()> {
    println!(
        "Request of take dispute {} from mostro pubId {}",
        dispute_id,
        ctx.mostro_pubkey.clone()
    );
    // Create takebuy message
    let take_dispute_message =
        Message::new_dispute(Some(*dispute_id), None, None, Action::AdminSettle, None)
            .as_json()
            .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    println!("Admin keys: {:?}", ctx.context_keys.public_key.to_string());
    admin_send_dm(ctx, take_dispute_message).await?;
    Ok(())
}

[src/cli/add_invoice.rs:12-84]

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but since is yet a reference coming from cli command no need to reference it again

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the actual pattern matching code in src/cli.rs
echo "=== Checking the actual pattern match in src/cli.rs ==="
rg -n "Commands::GetDm.*since.*from_user" src/cli.rs -A 2 -B 1

echo ""
echo "=== Checking the Commands enum definition ==="
rg -n "GetDm {" src/cli.rs -A 5

echo ""
echo "=== Checking execute_get_dm signature ==="
rg -n "pub async fn execute_get_dm" src/cli/get_dm.rs -A 3

Length of output: 508


🏁 Script executed:

#!/bin/bash
# Check the Commands enum GetDm variant definition
echo "=== Checking GetDm enum variant ==="
rg -n "GetDm \{" src/cli.rs -A 5

echo ""
echo "=== Alternative: check entire Commands enum around GetDm ==="
rg -n "/// Get the latest direct messages" src/cli.rs -A 10

echo ""
echo "=== Check if the code currently compiles ==="
cd /tmp && cargo check --manifest-path="${GITHUB_WORKSPACE}/Cargo.toml" 2>&1 | head -50

Length of output: 1710


@arkanoider, I understand the confusion, but there's actually a type mismatch here. Let me clarify:

When you pattern match on Commands::GetDm { since, from_user }, the since variable is an owned i64 value (not a reference), because the enum field is defined as:

GetDm {
    since: i64,  // ← owned value
    from_user: bool,
}

However, execute_get_dm expects a reference:

pub async fn execute_get_dm(
    since: &i64,  // ← expects a reference
    ...
)

So when you call execute_get_dm(since, ...), you're passing an i64 where an &i64 is expected. This is a type error in Rust.

You need to borrow since at the call sites:

Commands::GetDm { since, from_user } => {
    execute_get_dm(&since, false, from_user, ctx).await
}
Commands::GetAdminDm { since, from_user } => {
    execute_get_dm(&since, true, from_user, ctx).await
}

If your local code compiles without the &, then you might have already made this fix locally, or perhaps you've modified the function signature to accept i64 instead of &i64. Could you double-check what you have locally?

🐰


🧬 Code Graph Analysis Results
  • File: src/cli/adm_send_dm.rs, Lines 6-17
pub async fn execute_adm_send_dm(receiver: PublicKey, ctx: &Context, message: &str) -> Result<()> {
    println!(
        "SENDING DM with admin keys: {}",
        ctx.context_keys.public_key().to_hex()
    );

    send_admin_gift_wrap_dm(&ctx.client, &ctx.context_keys, &receiver, message).await?;

    println!("Admin gift wrap message sent to {}", receiver);

    Ok(())
}
  • File: src/cli/conversation_key.rs, Lines 5-17
pub async fn execute_conversation_key(trade_keys: &Keys, receiver: PublicKey) -> Result<()> {
    // Derive conversation key
    let ck = ConversationKey::derive(trade_keys.secret_key(), &receiver)?;
    let key = ck.as_bytes();
    let mut ck_hex = vec![];
    for i in key {
        ck_hex.push(format!("{:02x}", i));
    }
    let ck_hex = ck_hex.join("");
    println!("Conversation key: {:?}", ck_hex);

    Ok(())
}
  • File: src/cli/list_disputes.rs, Lines 7-21
pub async fn execute_list_disputes(ctx: &Context) -> Result<()> {
    // Print mostro pubkey
    println!(
        "Requesting disputes from mostro pubId - {}",
        &ctx.mostro_pubkey
    );

    // Get orders from relays
    let table_of_disputes =
        fetch_events_list(ListKind::Disputes, None, None, None, ctx, None).await?;
    let table = print_disputes_table(table_of_disputes)?;
    println!("{table}");

    Ok(())
}
  • File: src/cli/restore.rs, Lines 7-32
pub async fn execute_restore(
    identity_keys: &Keys,
    mostro_key: PublicKey,
    client: &Client,
) -> Result<()> {
    let restore_message = Message::new_restore(None);
    let message_json = restore_message
        .as_json()
        .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    // Send the restore message to Mostro server
    send_dm(
        client,
        Some(identity_keys),
        identity_keys,
        &mostro_key,
        message_json,
        None,
        false,
    )
    .await?;

    println!("Restore message sent successfully. Recovering pending orders and disputes...");

    Ok(())
}
  • File: src/cli/dm_to_user.rs, Lines 7-33
pub async fn execute_dm_to_user(
    receiver: PublicKey,
    client: &Client,
    order_id: &Uuid,
    message: &str,
    pool: &SqlitePool,
) -> Result<()> {
    // Get the order
    let order = Order::get_by_id(pool, &order_id.to_string())
        .await
        .map_err(|_| anyhow::anyhow!("order {} not found", order_id))?;
    // Get the trade keys
    let trade_keys = match order.trade_keys.as_ref() {
        Some(trade_keys) => Keys::parse(trade_keys)?,
        None => anyhow::bail!("No trade_keys found for this order"),
    };

    // Send the DM
    println!(
        "SENDING DM with trade keys: {}",
        trade_keys.public_key().to_hex()
    );

    send_gift_wrap_dm(client, &trade_keys, &receiver, message).await?;

    Ok(())
}
  • File: src/cli/get_dm.rs, Lines 10-38
pub async fn execute_get_dm(
    since: &i64,
    admin: bool,
    from_user: &bool,
    ctx: &Context,
) -> Result<()> {
    // Get the list kind
    let list_kind = match (admin, from_user) {
        (true, true) => ListKind::PrivateDirectMessagesUser,
        (true, false) => ListKind::DirectMessagesAdmin,
        (false, true) => ListKind::PrivateDirectMessagesUser,
        (false, false) => ListKind::DirectMessagesUser,
    };

    // Fetch the requested events
    let all_fetched_events =
        fetch_events_list(list_kind, None, None, None, ctx, Some(since)).await?;

    // Extract (Message, u64) tuples from Event::MessageTuple variants
    let mut dm_events: Vec<(Message, u64)> = Vec::new();
    for event in all_fetched_events {
        if let Event::MessageTuple(tuple) = event {
            dm_events.push(*tuple);
        }
    }

    print_direct_messages(&dm_events, &ctx.pool).await?;
    Ok(())
}
  • File: src/cli/send_dm.rs, Lines 8-47
pub async fn execute_send_dm(
    receiver: PublicKey,
    ctx: &Context,
    order_id: &Uuid,
    message: &str,
) -> Result<()> {
    let message = Message::new_dm(
        None,
        None,
        Action::SendDm,
        Some(Payload::TextMessage(message.to_string())),
    )
    .as_json()
    .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    let trade_keys =
        if let Ok(order_to_vote) = Order::get_by_id(&ctx.pool, &order_id.to_string()).await {
            match order_to_vote.trade_keys.as_ref() {
                Some(trade_keys) => Keys::parse(trade_keys)?,
                None => {
                    anyhow::bail!("No trade_keys found for this order");
                }
            }
        } else {
            return Err(anyhow::anyhow!("order {} not found", order_id));
        };

    send_dm(
        &ctx.client,
        None,
        &trade_keys,
        &receiver,
        message,
        None,
        false,
    )
    .await?;

    Ok(())
}
  • File: src/cli/rate_user.rs, Lines 19-59
pub async fn execute_rate_user(order_id: &Uuid, rating: &u8, ctx: &Context) -> Result<()> {
    // Check boundaries
    let rating_content = get_user_rate(rating)?;

    // Get the trade keys
    let trade_keys =
        if let Ok(order_to_vote) = Order::get_by_id(&ctx.pool, &order_id.to_string()).await {
            match order_to_vote.trade_keys.as_ref() {
                Some(trade_keys) => Keys::parse(trade_keys)?,
                None => {
                    return Err(anyhow::anyhow!("No trade_keys found for this order"));
                }
            }
        } else {
            return Err(anyhow::anyhow!("order {} not found", order_id));
        };

    // Create rating message of counterpart
    let rate_message = Message::new_order(
        Some(*order_id),
        None,
        None,
        Action::RateUser,
        Some(rating_content),
    )
    .as_json()
    .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    send_dm(
        &ctx.client,
        Some(&ctx.identity_keys),
        &trade_keys,
        &ctx.mostro_pubkey,
        rate_message,
        None,
        false,
    )
    .await?;

    Ok(())
}
  • File: src/cli/list_orders.rs, Lines 9-72
pub async fn execute_list_orders(
    kind: &Option<String>,
    currency: &Option<String>,
    status: &Option<String>,
    ctx: &Context,
) -> Result<()> {
    // Used to get upper currency string to check against a list of tickers
    let mut upper_currency: Option<String> = None;
    // Default status is pending
    let mut status_checked: Option<Status> = Some(Status::Pending);
    // Default kind is none
    let mut kind_checked: Option<mostro_core::order::Kind> = None;

    // New check against strings
    if let Some(s) = status {
        status_checked = Some(
            Status::from_str(s)
                .map_err(|e| anyhow::anyhow!("Not valid status '{}': {:?}", s, e))?,
        );
    }

    // Print status requested
    if let Some(status) = &status_checked {
        println!("You are searching orders with status {:?}", status);
    }
    // New check against strings for kind
    if let Some(k) = kind {
        kind_checked = Some(
            mostro_core::order::Kind::from_str(k)
                .map_err(|e| anyhow::anyhow!("Not valid order kind '{}': {:?}", k, e))?,
        );
        if let Some(kind) = &kind_checked {
            println!("You are searching {} orders", kind);
        }
    }

    // Uppercase currency
    if let Some(curr) = currency {
        upper_currency = Some(curr.to_uppercase());
        if let Some(currency) = &upper_currency {
            println!("You are searching orders with currency {}", currency);
        }
    }

    println!(
        "Requesting orders from mostro pubId - {}",
        &ctx.mostro_pubkey
    );

    // Get orders from relays
    let table_of_orders = fetch_events_list(
        ListKind::Orders,
        status_checked,
        upper_currency,
        kind_checked,
        ctx,
        None,
    )
    .await?;
    let table = print_orders_table(table_of_orders)?;
    println!("{table}");

    Ok(())
}
  • File: src/cli/take_order.rs, Lines 54-124
pub async fn execute_take_order(
    order_id: &Uuid,
    action: Action,
    invoice: &Option<String>,
    amount: Option<u32>,
    ctx: &Context,
) -> Result<()> {
    let action_name = match action {
        Action::TakeBuy => "take buy",
        Action::TakeSell => "take sell",
        _ => return Err(anyhow::anyhhow!("Invalid action for take order")),
    };

    println!(
        "Request of {} order {} from mostro pubId {}",
        action_name, order_id, ctx.mostro_pubkey
    );

    // Create payload based on action type
    let payload = create_take_order_payload(action.clone(), invoice, amount)?;

    // Create request id
    let request_id = Uuid::new_v4().as_u128() as u64;

    // Create message
    let take_order_message = Message::new_order(
        Some(*order_id),
        Some(request_id),
        Some(ctx.trade_index),
        action.clone(),
        payload,
    );

    // Send dm to receiver pubkey
    println!(
        "SENDING DM with trade keys: {:?}",
        ctx.trade_keys.public_key().to_hex()
    );

    let message_json = take_order_message
        .as_json()
        .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    // Send the DM
    // This is so we can wait for the gift wrap event in the main thread
    send_dm(
        &ctx.client,
        Some(&ctx.identity_keys),
        &ctx.trade_keys,
        &ctx.mostro_pubkey,
        message_json,
        None,
        false,
    )
    .await?;

    // Wait for the DM to be sent from mostro
    let recv_event = wait_for_dm(ctx, None).await?;

    // Parse the incoming DM
    let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await;
    if let Some(message) = messages.first() {
        let message = message.0.get_inner_message_kind();
        if message.request_id == Some(request_id) {
            if let Err(e) = print_commands_results(message, None, ctx).await {
                println!("Error in print_commands_results: {}", e);
            }
        }
    }
    Ok(())
}
  • File: src/cli/add_invoice.rs, Lines 12-84
pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context) -> Result<()> {
    let order = Order::get_by_id(&ctx.pool, &order_id.to_string()).await?;
    let trade_keys = order
        .trade_keys
        .clone()
        .ok_or(anyhow::anyhow!("Missing trade keys"))?;
    let order_trade_keys = Keys::parse(&trade_keys)?;
    println!(
        "Order trade keys: {:?}",
        order_trade_keys.public_key().to_hex()
    );

    println!(
        "Sending a lightning invoice for order {} to mostro pubId {}",
        order_id, ctx.mostro_pubkey
    );
    // Check invoice string
    let ln_addr = LightningAddress::from_str(invoice);
    let payload = if ln_addr.is_ok() {
        Some(Payload::PaymentRequest(None, invoice.to_string(), None))
    } else {
        match is_valid_invoice(invoice) {
            Ok(i) => Some(Payload::PaymentRequest(None, i.to_string(), None)),
            Err(e) => {
                return Err(anyhow::anyhow!("Invalid invoice: {}", e));
            }
        }
    };

    // Create request id
    let request_id = Uuid::new_v4().as_u128() as u64;
    // Create AddInvoice message
    let add_invoice_message = Message::new_order(
        Some(*order_id),
        Some(request_id),
        None,
        Action::AddInvoice,
        payload,
    );

    // Serialize the message
    let message_json = add_invoice_message
        .as_json()
        .map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

    // Send the DM
    send_dm(
        &ctx.client,
        Some(&ctx.identity_keys),
        &order_trade_keys,
        &ctx.mostro_pubkey,
        message_json,
        None,
        false,
    )
    .await?;

    // Wait for the DM to be sent from mostro
    let recv_event = wait_for_dm(ctx, Some(&order_trade_keys)).await?;

    // Parse the incoming DM
    let messages = parse_dm_events(recv_event, &order_trade_keys, None).await;
    if let Some(message) = messages.first() {
        let message = message.0.get_inner_message_kind();
        if message.request_id == Some(request_id) {
            if let Some(order) = Order::get_by_id(&ctx.pool, &order_id.to_string()).await.ok() {
                if let Err(e) = print_commands_results(message, Some(order.clone()), ctx).await {
                    println!("Error in print_commands_results: {}", e);
                }
            }
        }
    }

    Ok(())
}


// Admin commands
Expand Down
60 changes: 24 additions & 36 deletions src/cli/add_invoice.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::parser::dms::print_commands_results;
use crate::parser::parse_dm_events;
use crate::util::{send_dm, wait_for_dm};
use crate::{cli::Context, db::Order, lightning::is_valid_invoice};
use anyhow::Result;
Expand Down Expand Up @@ -52,45 +54,31 @@ pub async fn execute_add_invoice(order_id: &Uuid, invoice: &str, ctx: &Context)
.as_json()
.map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

// Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0
let subscription = Filter::new()
.pubkey(order_trade_keys.clone().public_key())
.kind(nostr_sdk::Kind::GiftWrap)
.limit(0);

let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEvents(1));
ctx.client.subscribe(subscription, Some(opts)).await?;

// Clone the keys and client for the async call
let identity_keys_clone = ctx.identity_keys.clone();
let client_clone = ctx.client.clone();
let mostro_pubkey_clone = ctx.mostro_pubkey;
let order_trade_keys_clone = order_trade_keys.clone();

// Spawn a new task to send the DM
// This is so we can wait for the gift wrap event in the main thread
tokio::spawn(async move {
let _ = send_dm(
&client_clone,
Some(&identity_keys_clone),
&order_trade_keys,
&mostro_pubkey_clone,
message_json,
None,
false,
)
.await;
});

// Wait for the DM to be sent from mostro and update the order
wait_for_dm(
// Send the DM
send_dm(
&ctx.client,
&order_trade_keys_clone,
request_id,
Some(&ctx.identity_keys),
&order_trade_keys,
&ctx.mostro_pubkey,
message_json,
None,
Some(order),
&ctx.pool,
false,
)
.await?;

Comment thread
arkanoider marked this conversation as resolved.
// Wait for the DM to be sent from mostro
let recv_event = wait_for_dm(ctx, Some(&order_trade_keys)).await?;

// Parse the incoming DM
let messages = parse_dm_events(recv_event, &order_trade_keys, None).await;
if let Some(message) = messages.first() {
let message = message.0.get_inner_message_kind();
if message.request_id == Some(request_id) {
if let Err(e) = print_commands_results(message, Some(order.clone()), ctx).await {
println!("Error in print_commands_results: {}", e);
}
}
}

Ok(())
}
5 changes: 3 additions & 2 deletions src/cli/get_dm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
};

pub async fn execute_get_dm(
since: Option<&i64>,
since: &i64,
admin: bool,
from_user: &bool,
ctx: &Context,
Expand All @@ -22,7 +22,8 @@ pub async fn execute_get_dm(
};

// Fetch the requested events
let all_fetched_events = { fetch_events_list(list_kind, None, None, None, ctx, since).await? };
let all_fetched_events =
fetch_events_list(list_kind, None, None, None, ctx, Some(since)).await?;

// Extract (Message, u64) tuples from Event::MessageTuple variants
let mut dm_events: Vec<(Message, u64)> = Vec::new();
Expand Down
70 changes: 32 additions & 38 deletions src/cli/new_order.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::cli::Context;
use crate::parser::dms::print_commands_results;
use crate::parser::orders::print_order_preview;
use crate::parser::parse_dm_events;
use crate::util::{send_dm, uppercase_first, wait_for_dm};
use anyhow::Result;
use mostro_core::prelude::*;
use nostr_sdk::prelude::*;
use std::collections::HashMap;
use std::io::{stdin, stdout, BufRead, Write};
use std::process;
Expand Down Expand Up @@ -119,7 +120,8 @@ pub async fn execute_new_order(

// Send dm to receiver pubkey
println!(
"SENDING DM with trade keys: {:?}",
"SENDING DM with trade index: {} and trade keys: {:?}",
ctx.trade_index,
ctx.trade_keys.public_key().to_hex()
);

Expand All @@ -128,47 +130,39 @@ pub async fn execute_new_order(
.as_json()
.map_err(|_| anyhow::anyhow!("Failed to serialize message"))?;

// Clone the keys and client for the async call
let identity_keys_clone = ctx.identity_keys.clone();
let trade_keys_clone = ctx.trade_keys.clone();
let client_clone = ctx.client.clone();
let mostro_pubkey_clone = ctx.mostro_pubkey;

// Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0
let subscription = Filter::new()
.pubkey(ctx.trade_keys.public_key())
.kind(nostr_sdk::Kind::GiftWrap)
.limit(0);

let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEvents(1));

ctx.client.subscribe(subscription, Some(opts)).await?;

// Spawn a new task to send the DM
// This is so we can wait for the gift wrap event in the main thread
tokio::spawn(async move {
let _ = send_dm(
&client_clone,
Some(&identity_keys_clone),
&trade_keys_clone,
&mostro_pubkey_clone,
message_json,
None,
false,
)
.await;
});

// Wait for the DM to be sent from mostro
wait_for_dm(
// Send the DM
send_dm(
&ctx.client,
Some(&ctx.identity_keys),
&ctx.trade_keys,
request_id,
Some(ctx.trade_index),
&ctx.mostro_pubkey,
message_json,
None,
&ctx.pool,
false,
)
.await?;

// Wait for the DM to be sent from mostro
let recv_event = wait_for_dm(ctx, None).await?;

// Parse the incoming DM
let messages = parse_dm_events(recv_event, &ctx.trade_keys, None).await;
if let Some((message, _, _)) = messages.first() {
let message = message.0.get_inner_message_kind();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if message.request_id == Some(request_id) {
print_commands_results(message, None, ctx).await?;
} else {
return Err(anyhow::anyhow!(
"Received response with mismatched request_id. Expected: {}, Got: {:?}",
request_id,
message.request_id
));
}
} else {
return Err(anyhow::anyhow!(
"No valid response received from Mostro after sending new order"
));
}

Ok(())
}
54 changes: 23 additions & 31 deletions src/cli/send_msg.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::cli::{Commands, Context};
use crate::db::{Order, User};
use crate::parser::dms::print_commands_results;
use crate::parser::parse_dm_events;
use crate::util::{send_dm, wait_for_dm};

use anyhow::Result;
Expand Down Expand Up @@ -27,12 +29,13 @@ pub async fn execute_send_msg(
}
};

println!(
"Sending {} command for order {:?} to mostro pubId {}",
requested_action,
order_id.as_ref(),
&ctx.mostro_pubkey
);
match order_id {
Some(id) => println!(
"Sending {} command for order {} to mostro pubId {}",
requested_action, id, ctx.mostro_pubkey
),
None => return Err(anyhow::anyhow!("Missing order id!")),
};

// Determine payload
let payload = match requested_action {
Expand All @@ -58,53 +61,42 @@ pub async fn execute_send_msg(

// Create and send the message
let message = Message::new_order(order_id, Some(request_id), None, requested_action, payload);
let idkey = ctx.identity_keys.to_owned();

if let Some(order_id) = order_id {
let order = Order::get_by_id(&ctx.pool, &order_id.to_string()).await?;

if let Some(trade_keys_str) = order.trade_keys.clone() {
let trade_keys = Keys::parse(&trade_keys_str)?;
// Subscribe to gift wrap events - ONLY NEW ONES WITH LIMIT 0
let subscription = Filter::new()
.pubkey(trade_keys.public_key())
.kind(nostr_sdk::Kind::GiftWrap)
.limit(0);

let opts =
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::WaitForEvents(1));
// Subscribe to gift wrap events
ctx.client.subscribe(subscription, Some(opts)).await?;
// Send DM
let message_json = message
.as_json()
.map_err(|e| anyhow::anyhow!("Failed to serialize message: {e}"))?;

// Send DM
send_dm(
&ctx.client,
Some(&idkey),
Some(&ctx.identity_keys),
&trade_keys,
&ctx.mostro_pubkey,
message_json,
None,
false,
)
.await
.map_err(|e| anyhow::anyhow!("Failed to send DM: {e}"))?;
.await?;

// Wait for the DM to be sent from mostro
wait_for_dm(
&ctx.client,
&trade_keys,
request_id,
None,
Some(order),
&ctx.pool,
)
.await
.map_err(|e| anyhow::anyhow!("Failed to wait for DM: {e}"))?;
// Wait for incoming DM
let recv_event = wait_for_dm(ctx, Some(&trade_keys)).await?;
let messages = parse_dm_events(recv_event, &trade_keys, None).await;
if let Some(message) = messages.first() {
let message = message.0.get_inner_message_kind();
println!("Message: {:?}", message);
if message.request_id == Some(request_id) {
let _ = print_commands_results(message, None, ctx).await;
}
}
}
}

Ok(())
}

Expand Down
Loading
Loading