diff --git a/taptrade-cli-demo/coordinator/src/communication/mod.rs b/taptrade-cli-demo/coordinator/src/communication/mod.rs index 53e1398..2bcb235 100755 --- a/taptrade-cli-demo/coordinator/src/communication/mod.rs +++ b/taptrade-cli-demo/coordinator/src/communication/mod.rs @@ -160,7 +160,7 @@ async fn request_offer_status_maker( Json(payload): Json, ) -> Result { let offer = database - .fetch_taken_offer_maker(&payload.order_id_hex, &payload.robohash_hex) + .fetch_taken_offer_maker(&payload.offer_id_hex, &payload.robohash_hex) .await?; match offer { Some(offer) => Ok(Json(OfferTakenResponse { @@ -196,13 +196,13 @@ async fn poll_escrow_confirmation( Json(payload): Json, ) -> Result { if !database - .is_valid_robohash_in_table(&payload.robohash_hex, &payload.order_id_hex) + .is_valid_robohash_in_table(&payload.robohash_hex, &payload.offer_id_hex) .await? { return Ok(StatusCode::NOT_FOUND.into_response()); } if database - .fetch_escrow_tx_confirmation_status(&payload.order_id_hex) + .fetch_escrow_tx_confirmation_status(&payload.offer_id_hex) .await? { return Ok(StatusCode::OK.into_response()); @@ -213,20 +213,19 @@ async fn poll_escrow_confirmation( async fn submit_obligation_confirmation( Extension(database): Extension>, - Extension(wallet): Extension>>, Json(payload): Json, ) -> Result { // sanity check if offer is in table and if the escrow tx is confirmed if !database - .is_valid_robohash_in_table(&payload.robohash_hex, &payload.order_id_hex) + .is_valid_robohash_in_table(&payload.robohash_hex, &payload.offer_id_hex) .await? || !database - .fetch_escrow_tx_confirmation_status(&payload.order_id_hex) + .fetch_escrow_tx_confirmation_status(&payload.offer_id_hex) .await? { return Ok(StatusCode::NOT_FOUND.into_response()); } database - .set_trader_happy_field(&payload.order_id_hex, &payload.robohash_hex, true) + .set_trader_happy_field(&payload.offer_id_hex, &payload.robohash_hex, true) .await?; Ok(StatusCode::OK.into_response()) } @@ -271,15 +270,44 @@ async fn poll_final_payout( { return Ok(StatusCode::NOT_FOUND.into_response()); } - // check if both traders are happy - // assemble payout psbt and return to them for signing + let trader_happiness = database + .fetch_trader_happiness(&payload.offer_id_hex) + .await?; + if trader_happiness.maker_happy.is_some_and(|x| x == true) + && trader_happiness.taker_happy.is_some_and(|x| x == true) + { + panic!("Implement wallet.assemble_keyspend_payout_psbt()"); + // let payout_keyspend_psbt_hex = wallet + // .assemble_keyspend_payout_psbt(&payload.offer_id_hex, &payload.robohash_hex) + // .await + // .context("Error assembling payout PSBT")?; + // return Ok(String::from(payout_keyspend_psbt_hex).into_response()); + } else if (trader_happiness.maker_happy.is_none() || trader_happiness.taker_happy.is_none()) + && !trader_happiness.escrow_ongoing + { + return Ok(StatusCode::ACCEPTED.into_response()); + } // if one of them is not happy // open escrow cli on coordinator to decide who will win (chat/dispute is out of scope for this demo) // once decided who will win assemble the correct payout psbt and return it to the according trader // the other trader gets a error code/ end of trade code - - panic!("implement") + // escrow winner has to be set true with a cli input of the coordinator. This could be an api + // endpoint for the admin UI frontend in the future + if let Some(escrow_winner) = database.fetch_escrow_result(&payload.offer_id_hex).await? { + if escrow_winner == payload.robohash_hex { + panic!("Implement wallet.assemble_script_payout_psbt()"); + // let script_payout_psbt_hex = wallet + // .assemble_script_payout_psbt(&payload.offer_id_hex, &payload.robohash_hex, is_maker_bool) + // .await + // .context("Error assembling payout PSBT")?; + // return Ok(String::from(payout_keyspend_psbt_hex).into_response()); + } else { + return Ok(StatusCode::GONE.into_response()); // this will be returned to the losing trader + } + } else { + return Ok(StatusCode::PROCESSING.into_response()); // this will be returned if the coordinator hasn't decided yet + } } async fn test_api() -> &'static str { diff --git a/taptrade-cli-demo/coordinator/src/database/mod.rs b/taptrade-cli-demo/coordinator/src/database/mod.rs index 52638d2..6a0da9d 100644 --- a/taptrade-cli-demo/coordinator/src/database/mod.rs +++ b/taptrade-cli-demo/coordinator/src/database/mod.rs @@ -40,6 +40,12 @@ struct AwaitingTakerOffer { musig_pubkey_hex_maker: String, } +pub struct TraderHappiness { + pub maker_happy: Option, + pub taker_happy: Option, + pub escrow_ongoing: bool, +} + fn bool_to_sql_int(flag: bool) -> Option { if flag { Some(1) @@ -126,6 +132,8 @@ impl CoordinatorDB { escrow_psbt_is_confirmed INTEGER, maker_happy INTEGER, taker_happy INTEGER, + escrow_ongoing INTEGER NOT NULL, + escrow_winner_robohash TEXT )", // escrow_psbt_is_confirmed will be set 1 once the escrow psbt is confirmed onchain ) .execute(&db_pool) @@ -351,8 +359,8 @@ impl CoordinatorDB { "INSERT OR REPLACE INTO taken_offers (offer_id, robohash_maker, robohash_taker, is_buy_order, amount_sat, bond_ratio, offer_duration_ts, bond_address_maker, bond_address_taker, bond_amount_sat, bond_tx_hex_maker, bond_tx_hex_taker, payout_address_maker, payout_address_taker, musig_pub_nonce_hex_maker, musig_pubkey_hex_maker, - musig_pub_nonce_hex_taker, musig_pubkey_hex_taker, escrow_psbt_hex_maker, escrow_psbt_hex_taker, escrow_psbt_txid, escrow_psbt_is_confirmed) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + musig_pub_nonce_hex_taker, musig_pubkey_hex_taker, escrow_psbt_hex_maker, escrow_psbt_hex_taker, escrow_psbt_txid, escrow_psbt_is_confirmed, escrow_ongoing) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ) .bind(public_offer.offer_id) .bind(public_offer.robohash_maker) @@ -376,6 +384,7 @@ impl CoordinatorDB { .bind(trade_contract_psbt_taker.clone()) .bind(trade_tx_txid) .bind(0) + .bind(0) .execute(&*self.db_pool) .await?; @@ -613,6 +622,44 @@ impl CoordinatorDB { .execute(&*self.db_pool) .await?; + if !is_happy { + sqlx::query("UPDATE taken_offers SET escrow_ongoing = 1 WHERE offer_id = ?") + .bind(offer_id) + .execute(&*self.db_pool) + .await?; + } + Ok(()) } + + pub async fn fetch_trader_happiness(&self, offer_id: &String) -> Result { + let row = sqlx::query( + "SELECT maker_happy, taker_happy, escrow_ongoing FROM taken_offers WHERE offer_id = ?", + ) + .bind(offer_id) + .fetch_one(&*self.db_pool) + .await?; + + let maker_happy: Option = row.try_get::, _>("maker_happy")?; + let taker_happy: Option = row.try_get::, _>("taker_happy")?; + let escrow_ongoing: i64 = row.try_get::("escrow_ongoing")?; + + Ok(TraderHappiness { + maker_happy: maker_happy.map(|v| v != 0), + taker_happy: taker_happy.map(|v| v != 0), + escrow_ongoing: escrow_ongoing != 0, + }) + } + + pub async fn fetch_escrow_result(&self, offer_id: &String) -> Result> { + let row = sqlx::query("SELECT escrow_winner_robohash FROM taken_offers WHERE offer_id = ?") + .bind(offer_id) + .fetch_one(&*self.db_pool) + .await?; + + let winner_robohash: Option = + row.try_get::, _>("escrow_winner_robohash")?; + + Ok(winner_robohash) + } } diff --git a/taptrade-cli-demo/trader/src/communication/mod.rs b/taptrade-cli-demo/trader/src/communication/mod.rs index 90ab1c2..c0af8ea 100644 --- a/taptrade-cli-demo/trader/src/communication/mod.rs +++ b/taptrade-cli-demo/trader/src/communication/mod.rs @@ -15,7 +15,7 @@ use bdk::{ wallet::AddressInfo, }; use serde::{Deserialize, Serialize}; -use std::{str::FromStr, thread::sleep, time::Duration}; +use std::{f32::consts::E, str::FromStr, thread::sleep, time::Duration}; impl BondRequirementResponse { fn _format_request(trader_setup: &TraderSettings) -> OrderRequest { @@ -257,13 +257,20 @@ impl IsOfferReadyRequest { .send()?; if res.status() == 200 { // good case, psbt is returned + debug!("Payout psbt received. Signing..."); break; - } else if res.status() == 204 { + } else if res.status() == 202 { // still waiting, retry continue; - } else if res.status() == 201 { + } else if res.status() == 102 { // other party initiated escrow - return Ok(None); + debug!("Other party initiated escrow. Waiting for coordinator to finalize."); + continue; + } else if res.status() != 410 { + return Err(anyhow!( + "We lost the escrow, your bond is gone: {}", + res.status() + )); } else { // unintended response return Err(anyhow!(