From 183eaf07f578c73ac01eef6394d010d1199dc93f Mon Sep 17 00:00:00 2001 From: fbock Date: Tue, 27 Aug 2024 16:41:42 +0200 Subject: [PATCH] many more comments, small improvements (faster polling) --- .../coordinator/src/coordinator/mod.rs | 40 ++++++++++- .../coordinator/src/wallet/escrow_psbt.rs | 3 + .../coordinator/src/wallet/payout_tx.rs | 16 ++++- .../trader/src/communication/mod.rs | 22 +++--- .../src/communication/taker_requests.rs | 1 + taptrade-cli-demo/trader/src/main.rs | 1 + .../trader/src/trading/maker_utils.rs | 5 ++ taptrade-cli-demo/trader/src/trading/mod.rs | 22 ++++-- .../trader/src/trading/taker_utils.rs | 8 ++- taptrade-cli-demo/trader/src/wallet/mod.rs | 71 ++++--------------- 10 files changed, 112 insertions(+), 77 deletions(-) diff --git a/taptrade-cli-demo/coordinator/src/coordinator/mod.rs b/taptrade-cli-demo/coordinator/src/coordinator/mod.rs index 23ab145..f2a5645 100755 --- a/taptrade-cli-demo/coordinator/src/coordinator/mod.rs +++ b/taptrade-cli-demo/coordinator/src/coordinator/mod.rs @@ -6,6 +6,8 @@ pub mod tx_confirmation_monitoring; use super::*; +/// Accepts the request to create a new offer, inserts it in the database and +/// returns the required bond information to the maker. pub async fn process_order( coordinator: Arc, offer: &OfferRequest, @@ -28,6 +30,8 @@ pub async fn process_order( Ok(bond_requirements) } +/// Accepts the signed bond transaction passed by the maker, validates it and inserts it in the database for further monitoring. +/// Moves the offer from the pending table to the active_maker_offers table ("the Orderbook"). pub async fn handle_maker_bond( payload: &BondSubmissionRequest, coordinator: Arc, @@ -35,23 +39,30 @@ pub async fn handle_maker_bond( let wallet = &coordinator.coordinator_wallet; let database = &coordinator.coordinator_db; + // get the according bond requirements from the database to validate against them let bond_requirements = database .fetch_bond_requirements(&payload.robohash_hex) .await .map_err(|_| BondError::BondNotFound)?; + // validate the signed bond transaction wallet .validate_bond_tx_hex(&payload.signed_bond_hex, &bond_requirements) .await .map_err(|e| BondError::InvalidBond(e.to_string()))?; debug!("\nBond validation successful"); + // generates a random offer id to be able to identify the offer let offer_id_hex: String = generate_random_order_id(16); // 16 bytes random offer id, maybe a different system makes more sense later on? (uuid or increasing counter...) // create address for taker bond + + // get new address for the taker bond to which the taker has to lock its bond when accepting this offer let new_taker_bond_address = wallet .get_new_address() .await .map_err(|e| BondError::CoordinatorError(e.to_string()))?; + // move the offer from the pending table to the active_maker_offers table in the database, returns the unix timestamp until the + // bond is being monitored (the inputs shouldn't be touched by the trader except for the following escrow transaction) let bond_locked_until_timestamp = database .move_offer_to_active(payload, &offer_id_hex, new_taker_bond_address) .await @@ -63,6 +74,7 @@ pub async fn handle_maker_bond( }) } +/// fetches all offers from the database that are suitable for the trade requested by the taker pub async fn get_public_offers( request: &OffersRequest, coordinator: Arc, @@ -80,6 +92,9 @@ pub async fn get_public_offers( Ok(PublicOffers { offers }) } +/// Accepts the request of the taker to take an offer, validates the taker bond tx that is passed with the request, +/// creates the escrow locking transaction and moves all information to the taken offers db table. Returns the +/// information necessary for the taker to sign its input to the escrow locking psbt pub async fn handle_taker_bond( payload: &OfferPsbtRequest, coordinator: Arc, @@ -87,11 +102,13 @@ pub async fn handle_taker_bond( let wallet = &coordinator.coordinator_wallet; let database = &coordinator.coordinator_db; + // fetch the bond requirements for the taker bond from the database let bond_requirements = database .fetch_taker_bond_requirements(&payload.offer.offer_id_hex) .await .map_err(|_| BondError::BondNotFound)?; + // validate the signed taker bond transaction against the requirements wallet .validate_bond_tx_hex(&payload.trade_data.signed_bond_hex, &bond_requirements) .await @@ -99,6 +116,7 @@ pub async fn handle_taker_bond( debug!("\nTaker bond validation successful"); + // create the escrow locking transaction let escrow_output_data = wallet .create_escrow_psbt(database, payload) .await @@ -108,6 +126,7 @@ pub async fn handle_taker_bond( escrow_output_data ); + // add the taker information to the database and move the offer to the taken_offers table database .add_taker_info_and_move_table(payload, &escrow_output_data) .await @@ -123,6 +142,10 @@ pub async fn handle_taker_bond( }) } +/// gets called by the polling endpoint the maker polls when waiting for an offer taker, +/// looks in the database if escrow output information is available for the offer id +/// which means the offer has been taken, returns the escrow locking tx information if +/// the offer has been taken so the maker can sign its input to it. pub async fn get_offer_status_maker( payload: &OfferTakenRequest, coordinator: Arc, @@ -157,6 +180,9 @@ pub async fn get_offer_status_maker( }) } +/// gets polled by both traders so they can see if the exchange can safely begin. +/// checks the database for the confirmation flag of the escrow transaction which is +/// set by the concurrent confirmation monitoring task pub async fn fetch_escrow_confirmation_status( payload: &OfferTakenRequest, coordinator: Arc, @@ -178,6 +204,9 @@ pub async fn fetch_escrow_confirmation_status( .map_err(|e| FetchEscrowConfirmationError::Database(e.to_string())) } +/// handles the returned signed escrow locking psbt of both traders, if both are present in the db +/// it combines them and broadcasts the escrow transaction to the network +/// otherwise the tx will just get stored in the db. pub async fn handle_signed_escrow_psbt( payload: &PsbtSubmissionRequest, coordinator: Arc, @@ -223,6 +252,7 @@ pub async fn handle_signed_escrow_psbt( Ok(()) } +/// sets the trader happy flag in the database which both traders have to either set true or false to continue with payout or escrow procedure pub async fn handle_obligation_confirmation( payload: &OfferTakenRequest, coordinator: Arc, @@ -237,6 +267,8 @@ pub async fn handle_obligation_confirmation( Ok(()) } +/// if a trader requests escrow this function sets the trader happy flag to false in the db. Then a CLI for the coordinator should be opened +/// to decide which trader is correct pub async fn initiate_escrow( payload: &TradeObligationsUnsatisfied, coordinator: Arc, @@ -252,6 +284,8 @@ pub async fn initiate_escrow( Ok(()) } +/// if both traders are happy this function will assemble the final keyspend payout transaction and return it to the traders +/// for them to be able to create the partial signatures pub async fn handle_final_payout( payload: &OfferTakenRequest, coordinator: Arc, @@ -263,6 +297,7 @@ pub async fn handle_final_payout( .await .map_err(|e| RequestError::Database(e.to_string()))?; + // both traders are happy, keyspend payout can begin if trader_happiness.maker_happy.is_some_and(|x| x) && trader_happiness.taker_happy.is_some_and(|x| x) { @@ -306,6 +341,7 @@ pub async fn handle_final_payout( agg_musig_nonce_hex: escrow_payout_data.agg_musig_nonce.to_string(), agg_musig_pubkey_ctx_hex: escrow_payout_data.aggregated_musig_pubkey_ctx_hex, })); + // at least one trader has not yet submitted the satisfaction request, or a escrow is already ongoing } else if (trader_happiness.maker_happy.is_none() || trader_happiness.taker_happy.is_none()) && !trader_happiness.escrow_ongoing { @@ -340,13 +376,13 @@ pub async fn handle_final_payout( } } +/// handles the returned partial signatures for the keyspend payout, if both are available it aggregates them, +/// inserts the signature in the payout tx and broadcasts it pub async fn handle_payout_signature( payload: &PayoutSignatureRequest, coordinator: Arc, ) -> Result { let database = &coordinator.coordinator_db; - // let _wallet = &coordinator.coordinator_wallet; - check_offer_and_confirmation(&payload.offer_id_hex, &payload.robohash_hex, database).await?; database diff --git a/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs b/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs index a28ac23..2b30978 100644 --- a/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs +++ b/taptrade-cli-demo/coordinator/src/wallet/escrow_psbt.rs @@ -208,11 +208,14 @@ impl CoordinatorWallet { }) } + /// placeholder to validate the returned, signed escrow psbt pub async fn validate_escrow_init_psbt(&self, _escrow_init_psbt: &str) -> Result<()> { warn!("Implement escrow psbt validation. For now, returning Ok"); Ok(()) } + /// combines the two signed, hex serialized escrow locking psbts returned by the traders, finalizes it + /// and broadcasts the escrow transaction pub async fn combine_and_broadcast_escrow_psbt( &self, signed_maker_psbt_hex: &str, diff --git a/taptrade-cli-demo/coordinator/src/wallet/payout_tx.rs b/taptrade-cli-demo/coordinator/src/wallet/payout_tx.rs index 082dfc8..955c7d5 100644 --- a/taptrade-cli-demo/coordinator/src/wallet/payout_tx.rs +++ b/taptrade-cli-demo/coordinator/src/wallet/payout_tx.rs @@ -4,6 +4,8 @@ use bdk::FeeRate; use super::*; use bitcoin; +/// get current feerate from blockchain backend and calculate absolute fees for the keyspend tx +/// depending on the feerate. Fallback to 40sat/vb if the feerate cannot be estimated (e.g. regtest backend). fn get_tx_fees_abs_sat(blockchain_backend: &RpcBlockchain) -> Result<(u64, u64)> { let feerate = match blockchain_backend.estimate_fee(6) { Ok(feerate) => feerate, @@ -20,6 +22,7 @@ fn get_tx_fees_abs_sat(blockchain_backend: &RpcBlockchain) -> Result<(u64, u64)> } impl CoordinatorWallet { + /// loads the escrow descriptor in a temp wallet and return the escrow utxo (as Input and its Outpoint) fn get_escrow_utxo( &self, descriptor: &Descriptor, @@ -45,6 +48,7 @@ impl CoordinatorWallet { Ok((input, outpoint)) } + /// assembles the keyspend payout transaction as PSBT (without signatures) pub async fn assemble_keyspend_payout_psbt( &self, payout_information: &PayoutData, @@ -77,33 +81,39 @@ impl CoordinatorWallet { Ok(payout_psbt.serialize_hex()) } + /// Inserts the aggregated signature into the keyspend transaction and broadcasts it pub async fn broadcast_keyspend_tx( &self, keyspend_ctx: &KeyspendContext, ) -> anyhow::Result<()> { + // we need a bitcoin 0.32 psbt to access the taproot_hash_ty() method let bitcoin_032_psbt = bitcoin::Psbt::from_str(&keyspend_ctx.keyspend_psbt.to_string())?; debug!("Payout psbt: {}", bitcoin_032_psbt.to_string()); + + // extract the unsigned transaction from the bitcoin 0.32 psbt let mut bitcoin_032_tx: bitcoin::Transaction = bitcoin_032_psbt.clone().extract_tx()?; + // get a secp256k1::schnorr::Signature from the aggregated musig signature let secp_signature = bitcoin::secp256k1::schnorr::Signature::from_slice(&keyspend_ctx.agg_sig.to_bytes())?; let sighash_type = bitcoin_032_psbt.inputs[0].taproot_hash_ty()?; + // assemble a rust bitcoin Signature from the secp signature and sighash type let rust_bitcoin_sig = bitcoin::taproot::Signature { signature: secp_signature, sighash_type, }; - // let unsigned_tx_hex = bitcoin::consensus::encode::serialize_hex(&bitcoin_032_tx); + // create a p2tr key spend witness from the rust bitcoin signature let witness = bitcoin::Witness::p2tr_key_spend(&rust_bitcoin_sig); - // let mut tx_clone = bitcoin_032_tx.clone(); + // insert the witness into the transaction let escrow_input: &mut bitcoin::TxIn = &mut bitcoin_032_tx.input[0]; escrow_input.witness = witness.clone(); let signed_hex_tx = bitcoin::consensus::encode::serialize_hex(&bitcoin_032_tx); - // convert the hex tx back into a bitcoin030 tx + // convert the hex tx back into a bitcoin030 tx to be able to broadcast it with the bdk backend let bdk_bitcoin_030_tx: bdk::bitcoin::Transaction = deserialize(&hex::decode(signed_hex_tx.clone())?)?; diff --git a/taptrade-cli-demo/trader/src/communication/mod.rs b/taptrade-cli-demo/trader/src/communication/mod.rs index 35ae060..a2ce5f7 100644 --- a/taptrade-cli-demo/trader/src/communication/mod.rs +++ b/taptrade-cli-demo/trader/src/communication/mod.rs @@ -45,6 +45,7 @@ impl BondRequirementResponse { trace!("Fetching bond requirements from coordinator. (create-offer)"); let client = reqwest::blocking::Client::new(); let endpoint = format!("{}{}", trader_setup.coordinator_endpoint, "/create-offer"); + // requests an offer from the coordinator according to the .env/cli input stored in trader_setup let res = match client .post(endpoint) .json(&Self::_format_order_request(trader_setup)) @@ -125,6 +126,7 @@ impl OfferTakenResponse { } impl PsbtSubmissionRequest { + // submits the signed escrow psbt to the coordinator pub fn submit_escrow_psbt( psbt: &PartiallySignedTransaction, offer_id_hex: String, @@ -157,10 +159,10 @@ impl TradeObligationsSatisfied { // if the trader is satisfied he can submit this to signal the coordinator readiness to close the trade // if the other party also submits this the coordinator can initiate the closing transaction, otherwise // escrow has to be initiated - pub fn submit(offer_id_hex: &String, trader_config: &TraderSettings) -> Result<()> { + pub fn submit(offer_id_hex: &str, trader_config: &TraderSettings) -> Result<()> { let request = TradeObligationsSatisfied { robohash_hex: trader_config.robosats_robohash_hex.clone(), - offer_id_hex: offer_id_hex.clone(), + offer_id_hex: offer_id_hex.to_string(), }; let client = reqwest::blocking::Client::new(); @@ -182,6 +184,7 @@ impl TradeObligationsSatisfied { } impl IsOfferReadyRequest { + /// polls until the escrow locking transaction is signaled as confirmed by the coordinator. This could also be implemented client side in theory pub fn poll(taker_config: &TraderSettings, offer: &ActiveOffer) -> Result<()> { let request = IsOfferReadyRequest { robohash_hex: taker_config.robosats_robohash_hex.clone(), @@ -204,11 +207,13 @@ impl IsOfferReadyRequest { res.status() )); } - // Sleep for 10 sec and poll again - sleep(Duration::from_secs(10)); + // Sleep for 2 sec and poll again + sleep(Duration::from_secs(2)); } } + /// polls until the other trader also confirmed happiness, then the payout data required to + /// create the partial signature for the keyspend payout is returned pub fn poll_payout( trader_config: &TraderSettings, offer: &ActiveOffer, @@ -221,8 +226,8 @@ impl IsOfferReadyRequest { let mut res: reqwest::blocking::Response; loop { - // Sleep for 10 sec and poll - sleep(Duration::from_secs(10)); + // Sleep for 2 sec and poll + sleep(Duration::from_secs(2)); res = client .post(format!( @@ -268,10 +273,11 @@ impl IsOfferReadyRequest { } impl TradeObligationsUnsatisfied { - pub fn request_escrow(offer_id_hex: &String, trader_config: &TraderSettings) -> Result<()> { + /// called to request escrow + pub fn request_escrow(offer_id_hex: &str, trader_config: &TraderSettings) -> Result<()> { let request = TradeObligationsUnsatisfied { robohash_hex: trader_config.robosats_robohash_hex.clone(), - offer_id_hex: offer_id_hex.clone(), + offer_id_hex: offer_id_hex.to_string(), }; let client = reqwest::blocking::Client::new(); diff --git a/taptrade-cli-demo/trader/src/communication/taker_requests.rs b/taptrade-cli-demo/trader/src/communication/taker_requests.rs index b09ef9a..c463d6b 100644 --- a/taptrade-cli-demo/trader/src/communication/taker_requests.rs +++ b/taptrade-cli-demo/trader/src/communication/taker_requests.rs @@ -62,6 +62,7 @@ impl PublicOffers { } impl OfferPsbtRequest { + /// submits the taker bond to the coordinator and receives the escrow PSBT to sign in exchange if the bond was accepted pub fn taker_request( offer: &PublicOffer, trade_data: BondSubmissionRequest, diff --git a/taptrade-cli-demo/trader/src/main.rs b/taptrade-cli-demo/trader/src/main.rs index 0abdb03..725c9ed 100644 --- a/taptrade-cli-demo/trader/src/main.rs +++ b/taptrade-cli-demo/trader/src/main.rs @@ -8,6 +8,7 @@ use anyhow::{anyhow, Result}; use cli::CliSettings; use log::{debug, error, info, trace, warn}; +/// start the according trading mode depending on the CLI input or env variables/.env file [maker or taker] fn start_trade_pipeline(cli_input: &CliSettings) -> Result<()> { match cli_input { CliSettings::Maker(maker_config) => trading::run_maker(maker_config), diff --git a/taptrade-cli-demo/trader/src/trading/maker_utils.rs b/taptrade-cli-demo/trader/src/trading/maker_utils.rs index a9dcc34..89c595d 100644 --- a/taptrade-cli-demo/trader/src/trading/maker_utils.rs +++ b/taptrade-cli-demo/trader/src/trading/maker_utils.rs @@ -11,11 +11,15 @@ impl ActiveOffer { trading_wallet: &TradingWallet, maker_config: &TraderSettings, ) -> Result { + // fetches the bond requirements necessary to assemble the bond for the requested offer let offer_conditions = BondRequirementResponse::fetch(maker_config)?; debug!("Offer conditions fetched: {:#?}", &offer_conditions); + // assembles the bond required by the coordinator, also generates the musig data (keys, nonces) and a payout address + // which are being submitted to the coordinator for the further trade let (bond, mut musig_data, payout_address) = trading_wallet.trade_onchain_assembly(&offer_conditions, maker_config)?; + // get necessary data for the coordinator to assemble the escrow locking psbt (inputs owned by maker, change address) let (psbt_inputs_hex_csv, escrow_change_address) = trading_wallet.get_escrow_psbt_inputs(offer_conditions.locking_amount_sat as i64)?; @@ -34,6 +38,7 @@ impl ActiveOffer { client_change_address: escrow_change_address.clone(), }; + // send the bond submission request to the coordinator, returns submission result with offer id and unix timestamp of bond lock let submission_result = bond_submission_request.send_maker(maker_config)?; Ok(ActiveOffer { offer_id_hex: submission_result.offer_id_hex, diff --git a/taptrade-cli-demo/trader/src/trading/mod.rs b/taptrade-cli-demo/trader/src/trading/mod.rs index 33895ee..f96a886 100644 --- a/taptrade-cli-demo/trader/src/trading/mod.rs +++ b/taptrade-cli-demo/trader/src/trading/mod.rs @@ -26,13 +26,16 @@ use communication::api::PayoutSignatureRequest; use reqwest::header::ACCEPT_LANGUAGE; use std::{str::FromStr, thread, time::Duration}; +/// the main maker flow function pub fn run_maker(maker_config: &TraderSettings) -> Result<()> { + // load a bdk wallet from passed xprv let wallet = TradingWallet::load_wallet(maker_config)?; // initialize the wallet with xprv + // create an offer with the coordinator, offer is an offer that is in the coordinator orderbook (bond submitted, awaiting taker) let offer = ActiveOffer::create(&wallet, maker_config)?; info!("Maker offer created: {:#?}", &offer); - // waits until taker accepts offer, then gets the escrow psbt in return to sign the inputs + // waits until taker accepts offer (polling), then gets the escrow psbt in return to sign the inputs let escrow_psbt_requirements = offer.wait_until_taken(maker_config)?; let mut escrow_psbt = PartiallySignedTransaction::from_str(escrow_psbt_requirements.escrow_psbt_hex.as_str())?; @@ -66,6 +69,7 @@ pub fn run_maker(maker_config: &TraderSettings) -> Result<()> { )?; // submit signed payout psbt back to coordinator PayoutSignatureRequest::send(maker_config, &signature, &offer.offer_id_hex)?; + // now the coordinator will broadcast the payout transaction and the trade should be finished } else { warn!("Trader unsatisfied. Initiating escrow mode."); TradeObligationsUnsatisfied::request_escrow(&offer.offer_id_hex, maker_config)?; @@ -74,26 +78,35 @@ pub fn run_maker(maker_config: &TraderSettings) -> Result<()> { Ok(()) } +/// taker main trade function pub fn run_taker(taker_config: &TraderSettings) -> Result<()> { let wallet = TradingWallet::load_wallet(taker_config)?; + + // fetches public offers of the coordinator (Orderbook) let mut available_offers = PublicOffers::fetch(taker_config)?; + // polls until offers are available while available_offers.offers.is_none() { - debug!("No offers available, fetching again in 10 sec."); - thread::sleep(Duration::from_secs(10)); + debug!("No offers available, fetching again in 2 sec."); + thread::sleep(Duration::from_secs(2)); available_offers = PublicOffers::fetch(taker_config)?; } + // ask for taker cli input to select a suitable offer let selected_offer: &PublicOffer = available_offers.ask_user_to_select()?; // take selected offer and wait for maker to sign his input to the ecrow transaction let accepted_offer = ActiveOffer::take(&wallet, taker_config, selected_offer)?; + + // polls the coordinator until the escrow locking transaction has been confirmed, this will take time until the maker signed his input + // and the transaction got confirmed onchain accepted_offer.wait_on_trade_ready_confirmation(taker_config)?; + // ask the taker if he is satisfied or wants to go into escrow with cli input if accepted_offer.fiat_confirmation_cli_input(taker_config)? { // this represents the "confirm payment" / "confirm fiat recieved" button TradeObligationsSatisfied::submit(&accepted_offer.offer_id_hex, taker_config)?; debug!("Waiting for other party to confirm the trade."); - // pull for other parties confirmation, then receive the transaction to create MuSig signature for (keyspend) to payout address + // pull for other parties confirmation, then receive the transaction to create MuSig partial signature for (keyspend) to payout address let (payout_keyspend_psbt, agg_pub_nonce, agg_pubk_ctx) = IsOfferReadyRequest::poll_payout(taker_config, &accepted_offer)?; @@ -109,6 +122,7 @@ pub fn run_taker(taker_config: &TraderSettings) -> Result<()> { // submit partial signature back to coordinator PayoutSignatureRequest::send(taker_config, &signature, &accepted_offer.offer_id_hex)?; + // now the coordinator will broadcast the payout transaction and the trade should be finished // here we need to handle if the other party is not cooperating } else { error!("Trader unsatisfied. Initiating escrow mode."); diff --git a/taptrade-cli-demo/trader/src/trading/taker_utils.rs b/taptrade-cli-demo/trader/src/trading/taker_utils.rs index 51f0128..7817f7e 100644 --- a/taptrade-cli-demo/trader/src/trading/taker_utils.rs +++ b/taptrade-cli-demo/trader/src/trading/taker_utils.rs @@ -21,8 +21,7 @@ impl ActiveOffer { let (bond, mut musig_data, payout_address) = trading_wallet.trade_onchain_assembly(&bond_requirements, taker_config)?; - // now we submit the signed bond transaction to the coordinator and receive the escrow PSBT we have to sign - // in exchange + // get inputs and a change address necessary for the coordinator to assemble the escrow locking psbt let (bdk_psbt_inputs_hex_csv, client_change_address) = trading_wallet.get_escrow_psbt_inputs(bond_requirements.locking_amount_sat as i64)?; @@ -36,11 +35,15 @@ impl ActiveOffer { bdk_psbt_inputs_hex_csv: bdk_psbt_inputs_hex_csv.clone(), client_change_address: client_change_address.clone(), }; + + // now we submit the signed bond transaction to the coordinator and receive the escrow PSBT we have to sign + // in exchange let escrow_contract_requirements = OfferPsbtRequest::taker_request(offer, bond_submission_request, taker_config)?; let mut escrow_psbt = PartiallySignedTransaction::from_str(&escrow_contract_requirements.escrow_psbt_hex)?; + // now we have to verify, sign and submit the escrow psbt again trading_wallet .validate_escrow_psbt(&escrow_psbt)? @@ -53,6 +56,7 @@ impl ActiveOffer { taker_config, )?; + // offer is now active Ok(ActiveOffer { offer_id_hex: offer.offer_id_hex.clone(), used_musig_config: musig_data, diff --git a/taptrade-cli-demo/trader/src/wallet/mod.rs b/taptrade-cli-demo/trader/src/wallet/mod.rs index efde2ff..993295f 100644 --- a/taptrade-cli-demo/trader/src/wallet/mod.rs +++ b/taptrade-cli-demo/trader/src/wallet/mod.rs @@ -98,54 +98,20 @@ impl TradingWallet { trader_config: &TraderSettings, ) -> Result<(PartiallySignedTransaction, MuSigData, AddressInfo)> { let trading_wallet = &self.wallet; + // assembles the bond according to the requirements let bond = Bond::assemble(&self.wallet, offer_conditions, trader_config)?; + + // get a new payout address from the trader wallet let payout_address: AddressInfo = trading_wallet.get_address(bdk::wallet::AddressIndex::New)?; + + // generate new musig nonce and keys from the wallet xprv let musig_data = MuSigData::create(&trader_config.wallet_xprv, trading_wallet.secp_ctx())?; Ok((bond, musig_data, payout_address)) } - // pub fn get_escrow_psbt( - // &self, - // escrow_psbt_requirements: OfferTakenResponse, - // trader_config: &TraderSettings, - // ) -> Result { - // let fee_output = Address::from_str(&escrow_psbt_requirements.escrow_tx_fee_address)? - // .assume_checked() - // .script_pubkey(); - // let escrow_output = { - // let temp_wallet = Wallet::new( - // &escrow_psbt_requirements.escrow_output_descriptor, - // None, - // Network::Regtest, - // MemoryDatabase::new(), - // )?; - // temp_wallet.get_address(AddressIndex::New)?.script_pubkey() - // }; - // self.wallet.sync(&self.backend, SyncOptions::default())?; - - // let escrow_amount_sat = match trader_config.trade_type { - // OfferType::Buy(_) => escrow_psbt_requirements.escrow_amount_taker_sat, - // OfferType::Sell(_) => escrow_psbt_requirements.escrow_amount_maker_sat, - // }; - // let (mut psbt, details) = { - // let mut builder = self.wallet.build_tx(); - // builder - // .add_recipient(escrow_output, escrow_amount_sat) - // .add_recipient( - // fee_output, - // escrow_psbt_requirements.escrow_fee_sat_per_participant, - // ) - // .fee_rate(FeeRate::from_sat_per_vb(10.0)); - // builder.finish()? - // }; - // debug!("Signing escrow psbt."); - // self.wallet.sign(&mut psbt, SignOptions::default())?; - // Ok(psbt) - // } - - /// returns suitable inputs (hex, csv serialized) and a change address for the assembly of the escrow psbt (coordinator side) + /// returns suitable inputs (binary encoded using bincode, hex serialized, csv formatted) and a change address for the assembly of the escrow psbt (coordinator side) pub fn get_escrow_psbt_inputs(&self, mut amount_sat: i64) -> Result<(String, String)> { let mut inputs: Vec = Vec::new(); @@ -175,15 +141,7 @@ impl TradingWallet { Ok((serialized_inputs, change_address)) } - // validate that the taker psbt references the correct inputs and amounts - // taker input should be the same as in the previous bond transaction. - // input amount should be the bond amount when buying, - // pub fn validate_taker_psbt(&self, psbt: &PartiallySignedTransaction) -> Result<&Self> { - // error!("IMPLEMENT TAKER PSBT VALIDATION!"); - // // tbd once the trade psbt is implemented on coordinator side - // Ok(self) - // } - + /// signs the inputs of the passed psbt that are controlled by the bdk wallet of the trader pub fn sign_escrow_psbt(&self, escrow_psbt: &mut PartiallySignedTransaction) -> Result<&Self> { // we need to finalize here too to make finalizing on the coordinator side work let sign_options = SignOptions { @@ -198,16 +156,14 @@ impl TradingWallet { pub fn validate_escrow_psbt(&self, psbt: &PartiallySignedTransaction) -> Result<&Self> { warn!("IMPLEMENT MAKER PSBT VALIDATION for production use!"); // validate: change output address, amounts, fee - // tbd once the trade psbt is implemented on coordinator side - + // tbd Ok(self) } pub fn validate_payout_psbt(&self, psbt: &PartiallySignedTransaction) -> Result<&Self> { warn!("IMPLEMENT PAYOUT PSBT VALIDATION for production use!"); // validate: change output address, amounts, fee - // tbd once the trade psbt is implemented on coordinator side - + // tbd Ok(self) } @@ -220,7 +176,6 @@ impl TradingWallet { agg_pub_nonce: AggNonce, local_musig_state: MuSigData, ) -> Result { - // let tweak = validated_payout_psbt.spend_info()?; let mut sig_hash_cache = SighashCache::new(&validated_payout_psbt.unsigned_tx); let utxo = validated_payout_psbt @@ -235,11 +190,13 @@ impl TradingWallet { .taproot_key_spend_signature_hash(0, &Prevouts::All(&[utxo]), sighash_type) .context("Failed to create keyspend sighash")?; let raw_sig_hash = binding.to_raw_hash(); - // let keyspend_sig_hash_msg = binding.as_byte_array(); + // get secret nonce from trader musig state let secret_nonce = local_musig_state.nonce.get_sec_for_signing()?; + // get secret key from trade musig state let seckey = local_musig_state.secret_key; + // create partial signature for the taproot keyspend signature hash of the payout psbt let keyspend_sig: musig2::PartialSignature = musig2::sign_partial( &key_agg_context, seckey, @@ -250,9 +207,7 @@ impl TradingWallet { match keyspend_sig { MaybeScalar::Valid(s) => Ok(s.encode_hex()), - MaybeScalar::Zero => { - Err(anyhow!("keyspend sig maybe scalar is Zero")) - } + MaybeScalar::Zero => Err(anyhow!("keyspend sig maybe scalar is Zero")), } } }