use crate::app::bond;
use crate::app::context::AppContext;
use crate::app::dispute::close_dispute_after_user_resolution;
use crate::lightning::LndConnector;
use crate::lnurl::resolv_ln_address;
use crate::nip33::{new_order_event, order_to_tags};
use crate::util::{enqueue_order_msg, get_order, settle_seller_hold_invoice, update_order_event};
use fedimint_tonic_lnd::lnrpc::payment::PaymentStatus;
use lnurl::lightning_address::LightningAddress;
use mostro_core::prelude::*;
use nostr_sdk::prelude::*;
use sqlx::{Pool, Sqlite};
use sqlx_crud::Crud;
use std::cmp::Ordering;
use std::str::FromStr;
use tokio::sync::mpsc::channel;
use tracing::info;
pub async fn check_failure_retries(
ctx: &AppContext,
order: &Order,
request_id: Option<u64>,
) -> Result<Order, MostroError> {
let mut order = order.clone();
let pool = ctx.pool();
let ln_settings = &ctx.settings().lightning;
let retries_number = ln_settings.payment_attempts as i64;
let is_first_failure = order.payment_attempts == 0;
order.count_failed_payment(retries_number);
let buyer_pubkey = order.get_buyer_pubkey().map_err(MostroInternalErr)?;
if is_first_failure {
let payment_failed_info = PaymentFailedInfo {
payment_attempts: ln_settings.payment_attempts.saturating_sub(1),
payment_retries_interval: ln_settings.payment_retries_interval,
};
enqueue_order_msg(
request_id,
Some(order.id),
Action::PaymentFailed,
Some(Payload::PaymentFailed(payment_failed_info)),
buyer_pubkey,
None,
)
.await;
} else if order.payment_attempts >= retries_number {
let mut order_payment_failed = order.clone();
order_payment_failed.amount = order_payment_failed.amount.saturating_sub(order.fee);
if order_payment_failed.amount <= 0 {
return Err(MostroCantDo(CantDoReason::InvalidAmount));
}
if mostro_core::order::Kind::from_str(&order.kind).is_err() {
return Err(MostroCantDo(CantDoReason::InvalidOrderKind));
}
if order_payment_failed.get_order_status().is_err() {
return Err(MostroInternalErr(ServiceError::InvalidOrderStatus));
}
enqueue_order_msg(
request_id,
Some(order.id),
Action::AddInvoice,
Some(Payload::Order(SmallOrder::from(
order_payment_failed.clone(),
))),
buyer_pubkey,
None,
)
.await;
}
sqlx::query("UPDATE orders SET failed_payment = ?, payment_attempts = ? WHERE id = ?")
.bind(order.failed_payment)
.bind(order.payment_attempts)
.bind(order.id)
.execute(pool)
.await
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
Ok(order)
}
pub async fn release_action(
ctx: &AppContext,
msg: Message,
event: &UnwrappedMessage,
my_keys: &Keys,
ln_client: &mut LndConnector,
) -> Result<(), MostroError> {
let pool = ctx.pool();
let request_id = msg.get_inner_message_kind().request_id;
let mut order = get_order(&msg, pool).await?;
let seller_pubkey = order.get_seller_pubkey().map_err(MostroInternalErr)?;
let buyer_pubkey = order.get_buyer_pubkey().map_err(MostroInternalErr)?;
if seller_pubkey != event.sender {
return Err(MostroCantDo(CantDoReason::InvalidPeer));
}
if order.check_status(Status::FiatSent).is_err() && order.check_status(Status::Dispute).is_err()
{
return Err(MostroCantDo(CantDoReason::NotAllowedByStatus));
}
let next_trade = msg
.get_inner_message_kind()
.get_next_trade_key()
.map_err(MostroInternalErr)?;
settle_seller_hold_invoice(event, ln_client, Action::Released, false, &order).await?;
order = update_order_event(my_keys, Status::SettledHoldInvoice, &order)
.await
.map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?;
let result =
sqlx::query("UPDATE orders SET status = ?, event_id = ? WHERE id = ? AND status IN (?, ?)")
.bind(&order.status)
.bind(&order.event_id)
.bind(order.id)
.bind(Status::FiatSent.to_string())
.bind(Status::Dispute.to_string())
.execute(pool)
.await
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
if result.rows_affected() == 0 {
tracing::warn!(
"Order {} not transitioned to settled-hold-invoice: status changed concurrently",
order.id
);
return Ok(());
}
close_dispute_after_user_resolution(ctx, &order, DisputeStatus::Settled, my_keys, "release")
.await;
enqueue_order_msg(
None,
Some(order.id),
Action::Released,
None,
buyer_pubkey,
None,
)
.await;
if let Ok((Some(child_order), Some(event))) = get_child_order(ctx, order.clone(), my_keys).await
{
let client = ctx.nostr_client();
if client.send_event(&event).await.is_err() {
tracing::warn!("Failed sending child order event for order id: {}. This may affect order synchronization", child_order.id)
}
handle_child_order(child_order, &order, next_trade, ctx.pool(), request_id)
.await
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
}
enqueue_order_msg(
request_id,
Some(order.id),
Action::HoldInvoicePaymentSettled,
None,
seller_pubkey,
None,
)
.await;
enqueue_order_msg(
None,
Some(order.id),
Action::Rate,
None,
seller_pubkey,
None,
)
.await;
bond::release_bonds_for_order_or_warn(pool, order.id, "release_action").await;
let _ = do_payment(ctx, order, request_id).await;
Ok(())
}
fn handle_buy_child_order(
child_order: &mut Order,
order: &Order,
normal_buyer_idkey: Option<String>,
) -> Result<(Option<String>, Option<i64>), MostroError> {
let next_buyer_pubkey = order.next_trade_pubkey.clone().ok_or_else(|| {
MostroInternalErr(ServiceError::UnexpectedError(
"Next trade buyer pubkey is missing".to_string(),
))
})?;
child_order.buyer_pubkey = Some(next_buyer_pubkey.clone());
child_order.trade_index_buyer = order.next_trade_index;
child_order.creator_pubkey = next_buyer_pubkey.clone();
match normal_buyer_idkey {
Some(idkey) => {
child_order.master_buyer_pubkey = Some(idkey);
}
None => {
child_order.master_buyer_pubkey = Some(next_buyer_pubkey);
}
}
child_order.next_trade_index = None;
child_order.next_trade_pubkey = None;
Ok((
child_order.buyer_pubkey.clone(),
child_order.trade_index_buyer,
))
}
fn handle_sell_child_order(
child_order: &mut Order,
next_trade: Option<(String, u32)>,
normal_seller_idkey: Option<String>,
) -> Result<(Option<String>, Option<i64>), MostroError> {
let (next_trade_pubkey, next_trade_index) = next_trade.ok_or_else(|| {
MostroInternalErr(ServiceError::UnexpectedError(
"Next trade seller pubkey is missing".to_string(),
))
})?;
let next_trade_pubkey = PublicKey::from_str(&next_trade_pubkey)
.map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?;
child_order.seller_pubkey = Some(next_trade_pubkey.to_string());
child_order.trade_index_seller = Some(next_trade_index as i64);
child_order.creator_pubkey = next_trade_pubkey.to_string();
match normal_seller_idkey {
Some(idkey) => {
child_order.master_seller_pubkey = Some(idkey);
}
None => {
child_order.master_seller_pubkey = Some(next_trade_pubkey.to_string());
}
}
Ok((
child_order.seller_pubkey.clone(),
child_order.trade_index_seller,
))
}
async fn handle_child_order(
mut child_order: Order,
order: &Order,
next_trade: Option<(String, u32)>,
pool: &Pool<Sqlite>,
request_id: Option<u64>,
) -> Result<(), MostroError> {
let (normal_buyer_idkey, normal_seller_idkey) =
order.is_full_privacy_order().map_err(|_| {
MostroInternalErr(ServiceError::UnexpectedError(
"Error creating order event".to_string(),
))
})?;
let (notification_pubkey, new_trade_index) = if order.is_buy_order().is_ok()
&& order.buyer_pubkey.as_ref() == Some(&order.creator_pubkey)
{
handle_buy_child_order(&mut child_order, order, normal_buyer_idkey)?
} else if order.is_sell_order().is_ok()
&& order.seller_pubkey.as_ref() == Some(&order.creator_pubkey)
{
handle_sell_child_order(&mut child_order, next_trade, normal_seller_idkey)?
} else {
return Err(MostroInternalErr(ServiceError::UnexpectedError(
"Invalid order type or creator".to_string(),
)));
};
let new_order = child_order.as_new_order();
if let (Some(destination_pubkey), new_trade_index) = (notification_pubkey, new_trade_index) {
enqueue_order_msg(
request_id,
new_order.id,
Action::NewOrder,
Some(Payload::Order(new_order)),
PublicKey::from_str(&destination_pubkey).map_err(|_| {
MostroInternalErr(ServiceError::NostrError("Invalid pubkey".to_string()))
})?,
new_trade_index,
)
.await;
} else {
return Err(MostroInternalErr(ServiceError::UnexpectedError(
"Next trade index or pubkey is missing - user cannot be notified".to_string(),
)));
}
child_order
.create(pool)
.await
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
Ok(())
}
pub async fn do_payment(
ctx: &AppContext,
mut order: Order,
request_id: Option<u64>,
) -> Result<(), MostroError> {
let payment_request = match order.buyer_invoice.as_ref() {
Some(req) => req.to_string(),
_ => return Err(MostroInternalErr(ServiceError::InvoiceInvalidError)),
};
let ln_addr = LightningAddress::from_str(&payment_request);
let amount = (order.amount as u64).saturating_sub(order.fee as u64);
if amount == 0 {
return Err(MostroInternalErr(ServiceError::InvoiceInvalidError));
}
let payment_request = if let Ok(addr) = ln_addr {
resolv_ln_address(&addr.to_string(), amount)
.await
.map_err(|_| MostroInternalErr(ServiceError::LnAddressParseError))?
} else {
payment_request
};
let mut ln_client_payment = LndConnector::new().await?;
let (tx, mut rx) = channel(100);
let payment_task = ln_client_payment.send_payment(&payment_request, amount as i64, tx);
if let Err(paymement_result) = payment_task.await {
info!("Error during ln payment : {}", paymement_result);
if let Ok(failed_payment) = check_failure_retries(ctx, &order, request_id).await {
info!(
"Order id {} has {} failed payments retries",
failed_payment.id, failed_payment.payment_attempts
);
}
}
let my_keys = ctx.keys().clone();
let buyer_pubkey = order.get_buyer_pubkey().map_err(MostroInternalErr)?;
let ctx = ctx.clone();
let payment = {
async move {
while let Some(msg) = rx.recv().await {
if let Ok(status) = PaymentStatus::try_from(msg.payment.status) {
match status {
PaymentStatus::Succeeded => {
info!(
"Order Id {}: Invoice with hash: {} paid!",
order.id, msg.payment.payment_hash
);
let _ = payment_success(
&ctx,
&mut order,
buyer_pubkey,
&my_keys,
request_id,
)
.await;
}
PaymentStatus::Failed => {
info!(
"Order Id {}: Invoice with hash: {} has failed!",
order.id, msg.payment.payment_hash
);
if let Ok(failed_payment) =
check_failure_retries(&ctx, &order, request_id).await
{
info!(
"Order id {} has {} failed payments retries",
failed_payment.id, failed_payment.payment_attempts
);
}
}
_ => {}
}
}
}
}
};
tokio::spawn(payment);
Ok(())
}
async fn payment_success(
ctx: &AppContext,
order: &mut Order,
buyer_pubkey: PublicKey,
my_keys: &Keys,
request_id: Option<u64>,
) -> Result<()> {
enqueue_order_msg(
None,
Some(order.id),
Action::PurchaseCompleted,
None,
buyer_pubkey,
None,
)
.await;
let pool = ctx.pool();
if let Ok(order_updated) = update_order_event(my_keys, Status::Success, order).await {
let result =
sqlx::query("UPDATE orders SET status = ?, event_id = ? WHERE id = ? AND status = ?")
.bind(&order_updated.status)
.bind(&order_updated.event_id)
.bind(order_updated.id)
.bind(Status::SettledHoldInvoice.to_string())
.execute(pool)
.await
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
if result.rows_affected() == 0 {
tracing::warn!(
"Order {} not transitioned to success: already processed by another task",
order_updated.id
);
return Ok(());
}
enqueue_order_msg(
request_id,
Some(order_updated.id),
Action::Rate,
None,
buyer_pubkey,
None,
)
.await;
}
Ok(())
}
pub async fn get_child_order(
ctx: &AppContext,
order: Order,
my_keys: &Keys,
) -> Result<(Option<Order>, Option<Event>), MostroError> {
let (Some(max_amount), Some(min_amount)) = (order.max_amount, order.min_amount) else {
return Ok((None, None));
};
if let Some(new_max) = max_amount.checked_sub(order.fiat_amount) {
let mut new_order = create_base_order(&order)?;
match new_max.cmp(&min_amount) {
Ordering::Equal => {
let (order, event) = order_for_equal(ctx, new_max, &mut new_order, my_keys).await?;
return Ok((Some(order), Some(event)));
}
Ordering::Greater => {
let (order, event) =
order_for_greater(ctx, new_max, &mut new_order, my_keys).await?;
return Ok((Some(order), Some(event)));
}
Ordering::Less => {
return Ok((None, None));
}
}
}
Ok((None, None))
}
fn create_base_order(order: &Order) -> Result<Order, MostroError> {
let mut new_order = order.clone();
new_order.id = uuid::Uuid::new_v4();
new_order.status = Status::Pending.to_string();
new_order.amount = 0;
new_order.hash = None;
new_order.preimage = None;
new_order.buyer_invoice = None;
new_order.taken_at = 0;
new_order.invoice_held_at = 0;
new_order.range_parent_id = Some(order.id);
match new_order.get_order_kind().map_err(MostroInternalErr)? {
mostro_core::order::Kind::Sell => {
new_order.buyer_pubkey = None;
new_order.master_buyer_pubkey = None;
new_order.trade_index_buyer = None;
}
mostro_core::order::Kind::Buy => {
new_order.seller_pubkey = None;
new_order.master_seller_pubkey = None;
new_order.trade_index_seller = None;
}
}
Ok(new_order)
}
async fn create_order_event(
ctx: &AppContext,
new_order: &mut Order,
my_keys: &Keys,
) -> Result<Event, MostroError> {
let pool = ctx.pool();
let identity_pubkey = match new_order.is_sell_order() {
Ok(_) => new_order
.get_master_seller_pubkey()
.map_err(MostroInternalErr)?,
Err(_) => new_order
.get_master_buyer_pubkey()
.map_err(MostroInternalErr)?,
};
let mostro_pubkey = my_keys.public_key().to_hex();
let tags = match crate::db::is_user_present(pool, identity_pubkey.to_string()).await {
Ok(user) => order_to_tags(
new_order,
Some((user.total_rating, user.total_reviews, user.created_at)),
Some(&mostro_pubkey),
)?,
Err(_) => order_to_tags(new_order, Some((0.0, 0, 0)), Some(&mostro_pubkey))?,
};
let event = if let Some(tags) = tags {
new_order_event(my_keys, "", new_order.id.to_string(), tags)
.map_err(|e| MostroInternalErr(ServiceError::NostrError(e.to_string())))?
} else {
return Err(MostroInternalErr(ServiceError::UnexpectedError(
"Error creating order event".to_string(),
)));
};
new_order.event_id = event.id.to_string();
Ok(event)
}
async fn order_for_equal(
ctx: &AppContext,
new_max: i64,
new_order: &mut Order,
my_keys: &Keys,
) -> Result<(Order, Event), MostroError> {
new_order.fiat_amount = new_max;
new_order.max_amount = None;
new_order.min_amount = None;
let event = create_order_event(ctx, new_order, my_keys).await?;
Ok((new_order.clone(), event))
}
async fn order_for_greater(
ctx: &AppContext,
new_max: i64,
new_order: &mut Order,
my_keys: &Keys,
) -> Result<(Order, Event), MostroError> {
new_order.max_amount = Some(new_max);
new_order.fiat_amount = 0;
let event = create_order_event(ctx, new_order, my_keys).await?;
Ok((new_order.clone(), event))
}