From 8a8a93e184126a8fdf91cfcd9c6fe637d14576d2 Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 24 Jun 2024 11:59:51 +0000 Subject: [PATCH] begin with coordinator --- taptrade-cli-demo/coordinator/.env | 1 + taptrade-cli-demo/coordinator/Cargo.lock | 14 +++ taptrade-cli-demo/coordinator/Cargo.toml | 7 ++ taptrade-cli-demo/coordinator/src/cli/mod.rs | 91 ------------------- .../coordinator/src/communication/mod.rs | 79 ++++++---------- taptrade-cli-demo/coordinator/src/main.rs | 23 +++-- taptrade-cli-demo/trader/Cargo.toml | 5 + .../trader/src/communication/api.rs | 5 + .../trader/src/communication/mod.rs | 81 ++++++++++++++++- .../src/communication/taker_requests.rs | 29 ------ taptrade-cli-demo/trader/src/trading/mod.rs | 8 +- taptrade-cli-demo/trader/todos.md | 3 +- 12 files changed, 159 insertions(+), 187 deletions(-) create mode 100644 taptrade-cli-demo/coordinator/.env delete mode 100755 taptrade-cli-demo/coordinator/src/cli/mod.rs diff --git a/taptrade-cli-demo/coordinator/.env b/taptrade-cli-demo/coordinator/.env new file mode 100644 index 0000000..bd8113e --- /dev/null +++ b/taptrade-cli-demo/coordinator/.env @@ -0,0 +1 @@ +ELECTRUM_BACKEND=127.0.0.1:50001 diff --git a/taptrade-cli-demo/coordinator/Cargo.lock b/taptrade-cli-demo/coordinator/Cargo.lock index dd74b5d..e074d91 100644 --- a/taptrade-cli-demo/coordinator/Cargo.lock +++ b/taptrade-cli-demo/coordinator/Cargo.lock @@ -66,6 +66,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + [[package]] name = "async-trait" version = "0.1.80" @@ -380,9 +386,11 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" name = "coordinator" version = "0.1.0" dependencies = [ + "anyhow", "axum", "bdk", "clap", + "dotenv", "frost-secp256k1", "reqwest", "serde", @@ -514,6 +522,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "ecdsa" version = "0.16.9" diff --git a/taptrade-cli-demo/coordinator/Cargo.toml b/taptrade-cli-demo/coordinator/Cargo.toml index c48462b..9cd2299 100644 --- a/taptrade-cli-demo/coordinator/Cargo.toml +++ b/taptrade-cli-demo/coordinator/Cargo.toml @@ -4,10 +4,17 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = "1.0.86" axum = "0.7.5" bdk = "0.29.0" clap = { version = "4.5.4", features = ["derive", "cargo"] } +dotenv = "0.15.0" frost-secp256k1 = "1.0.0" reqwest = { version = "0.12.4", features = ["blocking", "json"] } serde = "1.0.203" tokio = "1.38.0" + +[profile.release] +lto = true +opt-level = 3 +strip = true diff --git a/taptrade-cli-demo/coordinator/src/cli/mod.rs b/taptrade-cli-demo/coordinator/src/cli/mod.rs deleted file mode 100755 index 22b6a5c..0000000 --- a/taptrade-cli-demo/coordinator/src/cli/mod.rs +++ /dev/null @@ -1,91 +0,0 @@ -use clap::{command, Arg, Command, ArgMatches}; - -#[derive(Debug)] -pub struct Coordinator; - -#[derive(Debug)] -pub struct TraderSettings { - pub electrum_endpoint: String, - pub coordinator_endpoint: String, -} - -#[derive(Debug)] -pub enum CliSettings { - Coordinator(Coordinator), - Taker(TraderSettings), - Maker(TraderSettings) -} - -trait ArgMatchesParser { - fn parse_into_enum(&self) -> CliSettings; -} - -impl ArgMatchesParser for ArgMatches { - fn parse_into_enum(&self) -> CliSettings { - if let Some(_mode) = self.subcommand_matches("coordinator") { - CliSettings::Coordinator(Coordinator { }) - } else if let Some(mode) = self.subcommand_matches("trader") { - let trader_settings = TraderSettings { - coordinator_endpoint: mode.get_one::("coordinator-ep") - .expect("Coordinator endpoint not provided!").clone(), - electrum_endpoint: mode.get_one::("electrum-ep") - .expect("Electrum endpoint not provided").clone() - }; - if mode.contains_id("maker") { - CliSettings::Maker( trader_settings ) - } else if mode.contains_id("taker") { - CliSettings::Taker( trader_settings ) - } else { - panic!("Wrong arguments for Trader mode!") - } - } else { - panic!("Select either coordinator or trader mode!") - } - } -} - -pub fn parse_cli_args() -> CliSettings { - command!() - .about("RoboSats taproot onchain trade pipeline CLI demonstrator. Don't use with real funds.") - .subcommand( - Command::new("coordinator") - .about("Run in coordinator mode.") - ) - .subcommand( - Command::new("trader") - .about("Two available trader modes: Maker and Taker. Select one and provide Coordinator and Electum endpoint") - .arg( - Arg::new("taker") - .short('t') - .long("taker") - .help("Run program as taker") - .num_args(0) - .conflicts_with("maker") - ) - .arg ( - Arg::new("maker") - .short('m') - .long("maker") - .num_args(0) - .help("Run program as maker") - .conflicts_with("taker") - ) - .arg( - Arg::new("coordinator-ep") - .short('p') - .long("endpoint") - .required(true) - .help("Communication endpoint of the coordinator to connect to") - ) - .arg( - Arg::new("electrum-ep") - .short('e') - .long("electrum") - .required(true) - .help("URL of the electrum endpoint") - ) - ) - .arg_required_else_help(true) - .get_matches() - .parse_into_enum() -} diff --git a/taptrade-cli-demo/coordinator/src/communication/mod.rs b/taptrade-cli-demo/coordinator/src/communication/mod.rs index be5b600..5275043 100755 --- a/taptrade-cli-demo/coordinator/src/communication/mod.rs +++ b/taptrade-cli-demo/coordinator/src/communication/mod.rs @@ -1,17 +1,14 @@ -pub mod api; +mod api; + +use super::*; +use api::{BondRequirementResponse, BondSubmissionRequest, OrderActivatedResponse, OrderRequest}; +use axum::{routing::post, Json, Router}; -use reqwest::StatusCode; -use axum::{routing::post, Json, Router, response::{IntoResponse, Response}, }; -use serde::{Deserialize, Serialize}; use std::net::SocketAddr; use tokio::net::TcpListener; -use api::{ - BondRequirementResponse, BondSubmissionRequest, OrderActivatedResponse, OrderRequest, -}; - // Handler function to process the received data -async fn receive_order(Json(order): Json)-> Json { +async fn receive_order(Json(order): Json) -> Json { // Print the received data to the console println!("Received order: {:?}", order); @@ -23,61 +20,41 @@ async fn receive_order(Json(order): Json)-> Json, + Json(payload): Json, ) -> Json { - // Process the payload - // For now, we'll just return a dummy success response - let response = OrderActivatedResponse { - bond_locked_until_timestamp: 0 as u128, - order_id_hex: "Bond submitted successfully".to_string(), - }; + // Process the payload + // For now, we'll just return a dummy success response + let response = OrderActivatedResponse { + bond_locked_until_timestamp: 0 as u128, + order_id_hex: "Bond submitted successfully".to_string(), + }; - // Create the JSON response - Json(response) + // Create the JSON response + Json(response) } -#[tokio::main] -pub async fn webserver() { - // Build our application with a single route +pub async fn api_server() -> Result<()> { let app = Router::new() .route("/create-offer", post(receive_order)) .route("/submit-maker-bond", post(submit_maker_bond)); + // add other routes here - // Run the server on localhost:3000 - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + // Run the server on localhost:9999 + let addr = SocketAddr::from(([127, 0, 0, 1], 9999)); println!("Listening on {}", addr); - // axum::Server::bind(&addr) - // .serve(app.into_make_service()) - // .await - // .unwrap(); + let tcp = TcpListener::bind(&addr).await.unwrap(); - axum::serve(tcp, app).await.unwrap(); + axum::serve(tcp, app).await?; + Ok(()) } - -// // use axum - -// #[get("/")] -// fn index() -> &'static str { -// "Hello, world!" -// } - -// #[launch] -// pub fn webserver() -> Rocket { -// rocket::build().mount("/", routes![index]) -// } - -// // serde to parse json -// // https://www.youtube.com/watch?v=md-ecvXBGzI BDK + Webserver video -// // https://github.com/tokio-rs/axum diff --git a/taptrade-cli-demo/coordinator/src/main.rs b/taptrade-cli-demo/coordinator/src/main.rs index e5a2939..3707613 100755 --- a/taptrade-cli-demo/coordinator/src/main.rs +++ b/taptrade-cli-demo/coordinator/src/main.rs @@ -1,14 +1,17 @@ -mod coordinator; -mod cli; mod communication; +mod coordinator; -use cli::parse_cli_args; -use communication::webserver; +use anyhow::{anyhow, Result}; +use communication::api_server; +use dotenv::dotenv; +use serde::{Deserialize, Serialize}; +use std::env; -fn main() { - webserver(); - let mode = parse_cli_args(); - dbg!(mode); +// populate .env with values before starting +#[tokio::main] +async fn main() -> Result<()> { + dotenv().ok(); + + api_server().await?; + Ok(()) } - -// test with cargo run -- trader --maker --endpoint "taptrade-coordinator.com:5432" --electrum "electrum-server.com:50002" diff --git a/taptrade-cli-demo/trader/Cargo.toml b/taptrade-cli-demo/trader/Cargo.toml index 41f664e..ff92e94 100644 --- a/taptrade-cli-demo/trader/Cargo.toml +++ b/taptrade-cli-demo/trader/Cargo.toml @@ -15,3 +15,8 @@ rand_core = "0.6.4" reqwest = { version = "0.12.4", features = ["blocking", "json"] } serde = "1.0.203" sha2 = "0.10.8" + +[profile.release] +lto = true +opt-level = "z" +strip = true diff --git a/taptrade-cli-demo/trader/src/communication/api.rs b/taptrade-cli-demo/trader/src/communication/api.rs index b15c53c..ee31cce 100644 --- a/taptrade-cli-demo/trader/src/communication/api.rs +++ b/taptrade-cli-demo/trader/src/communication/api.rs @@ -100,3 +100,8 @@ pub struct TradeObligationsSatisfied { pub robohash_hex: String, pub offer_id_hex: String, } + +#[derive(Debug, Deserialize)] +pub struct PayoutPsbtResponse { + pub payout_psbt_hex: String, +} diff --git a/taptrade-cli-demo/trader/src/communication/mod.rs b/taptrade-cli-demo/trader/src/communication/mod.rs index 016596d..f464f99 100644 --- a/taptrade-cli-demo/trader/src/communication/mod.rs +++ b/taptrade-cli-demo/trader/src/communication/mod.rs @@ -8,8 +8,9 @@ use crate::{ }; use anyhow::{anyhow, Result}; use api::{ - BondRequirementResponse, BondSubmissionRequest, OfferTakenRequest, OfferTakenResponse, - OrderActivatedResponse, OrderRequest, PsbtSubmissionRequest, TradeObligationsSatisfied, + BondRequirementResponse, BondSubmissionRequest, IsOfferReadyRequest, OfferTakenRequest, + OfferTakenResponse, OrderActivatedResponse, OrderRequest, PayoutPsbtResponse, + PsbtSubmissionRequest, TradeObligationsSatisfied, }; use bdk::bitcoin::consensus::encode::serialize_hex; use bdk::{ @@ -17,7 +18,7 @@ use bdk::{ wallet::AddressInfo, }; use serde::{Deserialize, Serialize}; -use std::{thread::sleep, time::Duration}; +use std::{str::FromStr, thread::sleep, time::Duration}; impl BondRequirementResponse { fn _format_request(trader_setup: &TraderSettings) -> OrderRequest { @@ -183,3 +184,77 @@ impl TradeObligationsSatisfied { Ok(()) } } + +impl IsOfferReadyRequest { + pub fn poll(taker_config: &TraderSettings, offer: &ActiveOffer) -> Result<()> { + let request = IsOfferReadyRequest { + robohash_hex: taker_config.robosats_robohash_hex.clone(), + offer_id_hex: offer.offer_id_hex.clone(), + }; + let client = reqwest::blocking::Client::new(); + loop { + let res = client + .post(format!( + "{}{}", + taker_config.coordinator_endpoint, "/poll-offer-status" + )) + .json(&request) + .send()?; + if res.status() == 200 { + return Ok(()); + } else if res.status() != 204 { + return Err(anyhow!( + "Requesting offer status when waiting on other party failed: {}", + res.status() + )); + } + // Sleep for 10 sec and poll again + sleep(Duration::from_secs(10)); + } + } + + pub fn poll_payout( + trader_config: &TraderSettings, + offer: &ActiveOffer, + ) -> Result> { + let request = IsOfferReadyRequest { + robohash_hex: trader_config.robosats_robohash_hex.clone(), + offer_id_hex: offer.offer_id_hex.clone(), + }; + let client = reqwest::blocking::Client::new(); + let mut res: reqwest::blocking::Response; + + loop { + // Sleep for 10 sec and poll + sleep(Duration::from_secs(10)); + + res = client + .post(format!( + "{}{}", + trader_config.coordinator_endpoint, "/poll-final-payout" + )) + .json(&request) + .send()?; + if res.status() == 200 { + // good case, psbt is returned + break; + } else if res.status() == 204 { + // still waiting, retry + continue; + } else if res.status() == 201 { + // other party initiated escrow + return Ok(None); + } else { + // unintended response + return Err(anyhow!( + "Requesting final payout when waiting on other party failed: {}", + res.status() + )); + } + } + let final_psbt = PartiallySignedTransaction::from_str( + &res.json::()?.payout_psbt_hex, + )?; + Ok(Some(final_psbt)) + } +} diff --git a/taptrade-cli-demo/trader/src/communication/taker_requests.rs b/taptrade-cli-demo/trader/src/communication/taker_requests.rs index 02c5d36..0e903ba 100644 --- a/taptrade-cli-demo/trader/src/communication/taker_requests.rs +++ b/taptrade-cli-demo/trader/src/communication/taker_requests.rs @@ -83,32 +83,3 @@ impl OfferPsbtRequest { Ok(psbt) } } - -impl IsOfferReadyRequest { - pub fn poll(taker_config: &TraderSettings, offer: &ActiveOffer) -> Result<()> { - let request = IsOfferReadyRequest { - robohash_hex: taker_config.robosats_robohash_hex.clone(), - offer_id_hex: offer.offer_id_hex.clone(), - }; - let client = reqwest::blocking::Client::new(); - loop { - let res = client - .post(format!( - "{}{}", - taker_config.coordinator_endpoint, "/poll-offer-status-taker" - )) - .json(&request) - .send()?; - if res.status() == 200 { - return Ok(()); - } else if res.status() != 201 { - return Err(anyhow!( - "Requesting offer status when waiting on maker to sign psbt failed: {}", - res.status() - )); - } - // Sleep for 10 sec and poll again - sleep(Duration::from_secs(10)); - } - } -} diff --git a/taptrade-cli-demo/trader/src/trading/mod.rs b/taptrade-cli-demo/trader/src/trading/mod.rs index 9056d64..3e4d5e3 100644 --- a/taptrade-cli-demo/trader/src/trading/mod.rs +++ b/taptrade-cli-demo/trader/src/trading/mod.rs @@ -45,9 +45,10 @@ pub fn run_maker(maker_config: &TraderSettings) -> Result<()> { // wait for confirmation offer.wait_on_trade_ready_confirmation(maker_config)?; if offer.fiat_confirmation_cli_input(maker_config)? { + // this represents the "confirm payment" / "confirm fiat recieved" button TradeObligationsSatisfied::submit(&offer.offer_id_hex, maker_config)?; println!("Waiting for other party to confirm the trade."); - // poll for other party + let payout_keyspend_psbt = IsOfferReadyRequest::poll_payout(maker_config, &offer)?; } else { println!("Trade failed."); panic!("Escrow to be implemented!"); @@ -71,9 +72,12 @@ pub fn run_taker(taker_config: &TraderSettings) -> Result<()> { accepted_offer.wait_on_trade_ready_confirmation(taker_config)?; 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)?; println!("Waiting for other party to confirm the trade."); - // poll for other party + // pull for other parties confirmation, then receive the transaction to create MuSig signature for (keyspend) to payout address + let payout_keyspend_psbt = IsOfferReadyRequest::poll_payout(taker_config, &accepted_offer)?; + // here we need to handle if the other party is not cooperating } else { println!("Trade failed."); panic!("Escrow to be implemented!"); diff --git a/taptrade-cli-demo/trader/todos.md b/taptrade-cli-demo/trader/todos.md index 0601a09..6dd43f0 100644 --- a/taptrade-cli-demo/trader/todos.md +++ b/taptrade-cli-demo/trader/todos.md @@ -1 +1,2 @@ -tbd +Thinks to improve when implementing the production ready library: +* make api more generic (smaller)