mirror of
https://github.com/RoboSats/taptrade-core.git
synced 2025-07-29 14:11:39 +00:00
many more comments, small improvements (faster polling)
This commit is contained in:
@ -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<Coordinator>,
|
||||
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<Coordinator>,
|
||||
@ -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<Coordinator>,
|
||||
@ -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<Coordinator>,
|
||||
@ -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<Coordinator>,
|
||||
@ -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<Coordinator>,
|
||||
@ -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<Coordinator>,
|
||||
@ -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<Coordinator>,
|
||||
@ -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<Coordinator>,
|
||||
@ -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<Coordinator>,
|
||||
@ -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<Coordinator>,
|
||||
) -> Result<bool, RequestError> {
|
||||
let database = &coordinator.coordinator_db;
|
||||
// let _wallet = &coordinator.coordinator_wallet;
|
||||
|
||||
check_offer_and_confirmation(&payload.offer_id_hex, &payload.robohash_hex, database).await?;
|
||||
|
||||
database
|
||||
|
@ -208,11 +208,14 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
||||
})
|
||||
}
|
||||
|
||||
/// 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,
|
||||
|
@ -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<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
||||
/// 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<XOnlyPublicKey>,
|
||||
@ -45,6 +48,7 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
||||
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<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
|
||||
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())?)?;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -11,11 +11,15 @@ impl ActiveOffer {
|
||||
trading_wallet: &TradingWallet,
|
||||
maker_config: &TraderSettings,
|
||||
) -> Result<ActiveOffer> {
|
||||
// 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,
|
||||
|
@ -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.");
|
||||
|
@ -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,
|
||||
|
@ -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<PartiallySignedTransaction> {
|
||||
// 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<String> = 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<String> {
|
||||
// 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")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user