mirror of
https://github.com/RoboSats/taptrade-core.git
synced 2025-12-20 12:05:35 +00:00
taker
This commit is contained in:
1
taptrade-cli-demo/.rustfmt.toml
Normal file
1
taptrade-cli-demo/.rustfmt.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
hard_tabs = true
|
||||||
@ -1,68 +1,62 @@
|
|||||||
use axum::{
|
use axum::{routing::post, Json, Router};
|
||||||
routing::post,
|
|
||||||
Json, Router,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
struct OrderRequest {
|
struct OrderRequest {
|
||||||
robohash_base91: String,
|
robohash_base91: String,
|
||||||
amount_satoshi: u64,
|
amount_satoshi: u64,
|
||||||
order_type: String,
|
order_type: String,
|
||||||
bond_ratio: u8,
|
bond_ratio: u8,
|
||||||
}
|
}
|
||||||
// Handler function to process the received data
|
// Handler function to process the received data
|
||||||
async fn receive_order(Json(order): Json<OrderRequest>) {
|
async fn receive_order(Json(order): Json<OrderRequest>) {
|
||||||
// Print the received data to the console
|
// Print the received data to the console
|
||||||
println!("Received order: {:?}", order);
|
println!("Received order: {:?}", order);
|
||||||
|
|
||||||
// Access individual fields
|
|
||||||
let robohash = &order.robohash_base91;
|
|
||||||
let amount = order.amount_satoshi;
|
|
||||||
let order_type = &order.order_type;
|
|
||||||
let bond_ratio = order.bond_ratio;
|
|
||||||
|
|
||||||
// Process the data as needed
|
// Access individual fields
|
||||||
// For example, you can log the data, save it to a database, etc.
|
let robohash = &order.robohash_base91;
|
||||||
println!("Robohash: {}", robohash);
|
let amount = order.amount_satoshi;
|
||||||
println!("Amount (satoshi): {}", amount);
|
let order_type = &order.order_type;
|
||||||
println!("Order type: {}", order_type);
|
let bond_ratio = order.bond_ratio;
|
||||||
println!("Bond ratio: {}", bond_ratio);
|
|
||||||
|
|
||||||
// Example of further processing
|
// Process the data as needed
|
||||||
if order_type == "buy" {
|
// For example, you can log the data, save it to a database, etc.
|
||||||
println!("Processing a buy order...");
|
println!("Robohash: {}", robohash);
|
||||||
// Add your buy order logic here
|
println!("Amount (satoshi): {}", amount);
|
||||||
} else if order_type == "sell" {
|
println!("Order type: {}", order_type);
|
||||||
println!("Processing a sell order...");
|
println!("Bond ratio: {}", bond_ratio);
|
||||||
// Add your sell order logic here
|
|
||||||
}
|
// Example of further processing
|
||||||
|
if order_type == "buy" {
|
||||||
|
println!("Processing a buy order...");
|
||||||
|
// Add your buy order logic here
|
||||||
|
} else if order_type == "sell" {
|
||||||
|
println!("Processing a sell order...");
|
||||||
|
// Add your sell order logic here
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn webserver() {
|
pub async fn webserver() {
|
||||||
// Build our application with a single route
|
// Build our application with a single route
|
||||||
let app = Router::new().route("/receive-order", post(receive_order));
|
let app = Router::new().route("/receive-order", post(receive_order));
|
||||||
|
|
||||||
// Run the server on localhost:3000
|
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
|
||||||
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();
|
|
||||||
|
|
||||||
|
// Run the server on localhost:3000
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// // use axum
|
// // use axum
|
||||||
|
|
||||||
// #[get("/")]
|
// #[get("/")]
|
||||||
// fn index() -> &'static str {
|
// fn index() -> &'static str {
|
||||||
// "Hello, world!"
|
// "Hello, world!"
|
||||||
// }
|
// }
|
||||||
@ -74,4 +68,4 @@ pub async fn webserver() {
|
|||||||
|
|
||||||
// // serde to parse json
|
// // serde to parse json
|
||||||
// // https://www.youtube.com/watch?v=md-ecvXBGzI BDK + Webserver video
|
// // https://www.youtube.com/watch?v=md-ecvXBGzI BDK + Webserver video
|
||||||
// // https://github.com/tokio-rs/axum
|
// // https://github.com/tokio-rs/axum
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
ELECTRUM_ENDPOINT="ssl://mempool.space:40002" # testnet 4 electrum server
|
ELECTRUM_ENDPOINT="ssl://mempool.space:40002" # testnet 4 electrum server
|
||||||
COORDINATOR_ENDPOINT="http://127.0.0.1:3000"
|
COORDINATOR_ENDPOINT="http://127.0.0.1:3000"
|
||||||
ROBOHASH_BASE91="o!kfNkee;RF?m6FT87:1bjASE2/(1NFTnR;ILmHB<1p/vP%STz~IWolNDJa.#PmTI)sfBk+Gs!,(w8M" # "robot21"
|
ROBOHASH_BASE91="o!kfNkee;RF?m6FT87:1bjASE2/(1NFTnR;ILmHB<1p/vP%STz~IWolNDJa.#PmTI)sfBk+Gs!,(w8M" # "base91 of hex of sha256 of robot21"
|
||||||
TRADE_TYPE="buy"
|
TRADE_TYPE="buy"
|
||||||
PAYOUT_ADDRESS="tb1p45daj2eaza6drcd85c3wvn0zrpqxuduk3rzcmla4eu7a02cep9kqjzkc64"
|
PAYOUT_ADDRESS="tb1p45daj2eaza6drcd85c3wvn0zrpqxuduk3rzcmla4eu7a02cep9kqjzkc64"
|
||||||
BOND_RATIO=5
|
BOND_RATIO=5
|
||||||
@ -1,7 +1,7 @@
|
|||||||
use std::io::{self, Write};
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Digest, Sha256};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
use crate::wallet::{get_wallet_xprv, WalletDescriptors};
|
use crate::wallet::{get_wallet_xprv, WalletDescriptors};
|
||||||
use bdk::bitcoin::bip32::ExtendedPrivKey;
|
use bdk::bitcoin::bip32::ExtendedPrivKey;
|
||||||
@ -11,131 +11,144 @@ pub struct Coordinator;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum OfferType {
|
pub enum OfferType {
|
||||||
Buy(u64),
|
Buy(u64),
|
||||||
Sell(u64),
|
Sell(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TraderSettings {
|
pub struct TraderSettings {
|
||||||
pub electrum_endpoint: String,
|
pub electrum_endpoint: String,
|
||||||
pub coordinator_endpoint: String,
|
pub coordinator_endpoint: String,
|
||||||
pub robosats_robohash_base91: String,
|
pub robosats_robohash_base91: String,
|
||||||
pub trade_type: OfferType,
|
pub trade_type: OfferType,
|
||||||
pub payout_address: String,
|
pub payout_address: String,
|
||||||
pub bond_ratio: u8,
|
pub bond_ratio: u8,
|
||||||
pub wallet_xprv: ExtendedPrivKey,
|
pub wallet_xprv: ExtendedPrivKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CliSettings {
|
pub enum CliSettings {
|
||||||
Coordinator(Coordinator),
|
Coordinator(Coordinator),
|
||||||
Taker(TraderSettings),
|
Taker(TraderSettings),
|
||||||
Maker(TraderSettings)
|
Maker(TraderSettings),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hash256(input: &String) -> [u8; 32] {
|
fn hash256(input: &String) -> [u8; 32] {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(input.as_bytes());
|
hasher.update(input.as_bytes());
|
||||||
hasher.finalize().into()
|
hasher.finalize().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Robosats uses base91 encoded sha256 hash of the private robot key
|
// Robosats uses base91 encoded sha256 hash of the private robot key
|
||||||
fn bytes_to_base91(input: &[u8; 32]) -> String {
|
fn bytes_to_base91(input: &[u8; 32]) -> String {
|
||||||
let encoded_robohash: String = base91::EncodeIterator::new(input.iter().copied())
|
let encoded_robohash: String = base91::EncodeIterator::new(input.iter().copied())
|
||||||
.as_char_iter()
|
.as_char_iter()
|
||||||
.collect();
|
.collect();
|
||||||
encoded_robohash
|
encoded_robohash
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OfferType {
|
impl OfferType {
|
||||||
pub fn value(&self) -> u64 {
|
pub fn value(&self) -> u64 {
|
||||||
match self {
|
match self {
|
||||||
OfferType::Buy(value) => *value,
|
OfferType::Buy(value) => *value,
|
||||||
OfferType::Sell(value) => *value,
|
OfferType::Sell(value) => *value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CliSettings {
|
impl CliSettings {
|
||||||
fn get_user_input(prompt: &str) -> String {
|
fn get_user_input(prompt: &str) -> String {
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
print!("{}", prompt);
|
print!("{}", prompt);
|
||||||
io::stdout().flush().unwrap();
|
io::stdout().flush().unwrap();
|
||||||
io::stdin()
|
io::stdin()
|
||||||
.read_line(&mut buffer)
|
.read_line(&mut buffer)
|
||||||
.expect("Failed to read line!");
|
.expect("Failed to read line!");
|
||||||
buffer.trim().to_string()
|
buffer.trim().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_trade_type(trade_type: Option<String>) -> OfferType {
|
fn get_trade_type(trade_type: Option<String>) -> OfferType {
|
||||||
let trade_type = match trade_type {
|
let trade_type = match trade_type {
|
||||||
Some(value) => value,
|
Some(value) => value,
|
||||||
None => Self::get_user_input("Do you want to buy or sell satoshis: "),
|
None => Self::get_user_input("Do you want to buy or sell satoshis: "),
|
||||||
};
|
};
|
||||||
match trade_type.as_str() {
|
match trade_type.as_str() {
|
||||||
"buy" => OfferType::Buy(Self::get_user_input("How many satoshi do you want to buy: ").parse().unwrap()),
|
"buy" => OfferType::Buy(
|
||||||
"sell" => OfferType::Sell(Self::get_user_input("How many satoshi do you want to sell: ").parse().unwrap()),
|
Self::get_user_input("How many satoshi do you want to buy: ")
|
||||||
_ => panic!("Wrong offer type, you can only buy or sell"),
|
.parse()
|
||||||
}
|
.unwrap(),
|
||||||
}
|
),
|
||||||
|
"sell" => OfferType::Sell(
|
||||||
|
Self::get_user_input("How many satoshi do you want to sell: ")
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
_ => panic!("Wrong offer type, you can only buy or sell"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_trader_settings() -> Result<TraderSettings> {
|
fn get_trader_settings() -> Result<TraderSettings> {
|
||||||
let electrum_endpoint = Self::get_user_input("Enter electrum endpoint: ");
|
let electrum_endpoint = Self::get_user_input("Enter electrum endpoint: ");
|
||||||
let coordinator_endpoint = Self::get_user_input("Enter coordinator endpoint: ");
|
let coordinator_endpoint = Self::get_user_input("Enter coordinator endpoint: ");
|
||||||
let robosats_robohash_base91 = bytes_to_base91(&hash256(&Self::get_user_input("Enter your robosats robot key: ")));
|
let robosats_robohash_base91 = bytes_to_base91(&hash256(&Self::get_user_input(
|
||||||
let trade_type: OfferType = Self::get_trade_type(None);
|
"Enter your robosats robot key: ",
|
||||||
let payout_address = Self::get_user_input("Enter a payout address for refunded bonds or your trade payout: "); // bdk can be used for validation
|
)));
|
||||||
let bond_ratio: u8 = Self::get_user_input("Enter bond ration in [2, 50]%: ").parse()?;
|
let trade_type: OfferType = Self::get_trade_type(None);
|
||||||
let wallet_xprv = Self::check_xprv_input(Some(Self::get_user_input("Enter funded testnet wallet xprv or leave empty to generate: ")))?;
|
let payout_address = Self::get_user_input(
|
||||||
Ok(TraderSettings {
|
"Enter a payout address for refunded bonds or your trade payout: ",
|
||||||
electrum_endpoint,
|
); // bdk can be used for validation
|
||||||
coordinator_endpoint,
|
let bond_ratio: u8 = Self::get_user_input("Enter bond ration in [2, 50]%: ").parse()?;
|
||||||
robosats_robohash_base91,
|
let wallet_xprv = Self::check_xprv_input(Some(Self::get_user_input(
|
||||||
trade_type,
|
"Enter funded testnet wallet xprv or leave empty to generate: ",
|
||||||
payout_address,
|
)))?;
|
||||||
bond_ratio,
|
Ok(TraderSettings {
|
||||||
wallet_xprv
|
electrum_endpoint,
|
||||||
})
|
coordinator_endpoint,
|
||||||
}
|
robosats_robohash_base91,
|
||||||
|
trade_type,
|
||||||
|
payout_address,
|
||||||
|
bond_ratio,
|
||||||
|
wallet_xprv,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn check_xprv_input(cli_input: Option<String>) -> Result<ExtendedPrivKey> {
|
fn check_xprv_input(cli_input: Option<String>) -> Result<ExtendedPrivKey> {
|
||||||
if let Some(user_input) = cli_input {
|
if let Some(user_input) = cli_input {
|
||||||
if !(user_input.is_empty()) {
|
if !(user_input.is_empty()) {
|
||||||
return get_wallet_xprv(Some(user_input));
|
return get_wallet_xprv(Some(user_input));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
get_wallet_xprv(None)
|
get_wallet_xprv(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_from_env() -> Result<TraderSettings> {
|
fn load_from_env() -> Result<TraderSettings> {
|
||||||
dotenv::from_filename(".env")?;
|
dotenv::from_filename(".env")?;
|
||||||
Ok(TraderSettings {
|
Ok(TraderSettings {
|
||||||
electrum_endpoint: env::var("ELECTRUM_ENDPOINT")?,
|
electrum_endpoint: env::var("ELECTRUM_ENDPOINT")?,
|
||||||
coordinator_endpoint: env::var("COORDINATOR_ENDPOINT")?,
|
coordinator_endpoint: env::var("COORDINATOR_ENDPOINT")?,
|
||||||
robosats_robohash_base91: env::var("ROBOHASH_BASE91")?,
|
robosats_robohash_base91: env::var("ROBOHASH_BASE91")?,
|
||||||
trade_type: Self::get_trade_type(Some(env::var("TRADE_TYPE")?)),
|
trade_type: Self::get_trade_type(Some(env::var("TRADE_TYPE")?)),
|
||||||
payout_address: env::var("PAYOUT_ADDRESS")?,
|
payout_address: env::var("PAYOUT_ADDRESS")?,
|
||||||
bond_ratio: env::var("BOND_RATIO")?.parse()?,
|
bond_ratio: env::var("BOND_RATIO")?.parse()?,
|
||||||
wallet_xprv: Self::check_xprv_input(Some(env::var("XPRV")?))?,
|
wallet_xprv: Self::check_xprv_input(Some(env::var("XPRV")?))?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_cli_args() -> Result<Self> {
|
pub fn parse_cli_args() -> Result<Self> {
|
||||||
let mode = Self::get_user_input("Enter mode, 'taker' or 'maker': ");
|
let mode = Self::get_user_input("Enter mode, 'taker' or 'maker': ");
|
||||||
let trader_settings = match Self::get_user_input("Load from .env (y/N): ").trim() {
|
let trader_settings = match Self::get_user_input("Load from .env (y/N): ").trim() {
|
||||||
"y" => Self::load_from_env()?,
|
"y" => Self::load_from_env()?,
|
||||||
"N" => Self::get_trader_settings()?,
|
"N" => Self::get_trader_settings()?,
|
||||||
_ => return Err(anyhow!("Not a valid input!")),
|
_ => return Err(anyhow!("Not a valid input!")),
|
||||||
};
|
};
|
||||||
match mode.to_lowercase().as_str() {
|
match mode.to_lowercase().as_str() {
|
||||||
"maker" => Ok(Self::Maker(trader_settings)),
|
"maker" => Ok(Self::Maker(trader_settings)),
|
||||||
"taker" => Ok(Self::Taker(trader_settings)),
|
"taker" => Ok(Self::Taker(trader_settings)),
|
||||||
_ => Err(anyhow!("Either select maker or taker!")),
|
_ => Err(anyhow!("Either select maker or taker!")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// old cli parser using clap
|
// old cli parser using clap
|
||||||
|
|
||||||
// use clap::{command, Arg, Command, ArgMatches};
|
// use clap::{command, Arg, Command, ArgMatches};
|
||||||
|
|||||||
@ -2,22 +2,22 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct OfferCreationResponse {
|
pub struct OfferCreationResponse {
|
||||||
pub bond_address: String,
|
pub bond_address: String,
|
||||||
pub locking_amount: u64, // validate
|
pub locking_amount: u64, // validate
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct OrderRequest {
|
pub struct OrderRequest {
|
||||||
pub robohash_base91: String,
|
pub robohash_base91: String,
|
||||||
pub amount_satoshi: u64,
|
pub amount_satoshi: u64,
|
||||||
pub order_type: String, // buy or sell
|
pub order_type: String, // buy or sell
|
||||||
pub bond_ratio: u8 // [2, 50]
|
pub bond_ratio: u8, // [2, 50]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct BondSubmissionRequest {
|
pub struct BondSubmissionRequest {
|
||||||
pub serialized_bond_tx_hex: String,
|
pub serialized_bond_tx_hex: String,
|
||||||
pub robohash_base91: String,
|
pub robohash_base91: String,
|
||||||
pub payout_address: String,
|
pub payout_address: String,
|
||||||
pub frost_public_nonce_hex: String,
|
pub frost_public_nonce_hex: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,38 +3,41 @@ pub mod api;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::cli::{TraderSettings, OfferType};
|
use crate::cli::{OfferType, TraderSettings};
|
||||||
use api::{OrderRequest, OfferCreationResponse};
|
use api::{OfferCreationResponse, OrderRequest};
|
||||||
|
|
||||||
impl OfferCreationResponse {
|
impl OfferCreationResponse {
|
||||||
fn _format_request(trader_setup: &TraderSettings) -> OrderRequest {
|
fn _format_request(trader_setup: &TraderSettings) -> OrderRequest {
|
||||||
let amount: u64;
|
let amount: u64;
|
||||||
let trade_type = match &trader_setup.trade_type {
|
let trade_type = match &trader_setup.trade_type {
|
||||||
OfferType::Buy(val) => {
|
OfferType::Buy(val) => {
|
||||||
amount = *val;
|
amount = *val;
|
||||||
"buy"
|
"buy"
|
||||||
},
|
}
|
||||||
OfferType::Sell(val) => {
|
OfferType::Sell(val) => {
|
||||||
amount = *val;
|
amount = *val;
|
||||||
"sell"
|
"sell"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
OrderRequest {
|
OrderRequest {
|
||||||
robohash_base91: trader_setup.robosats_robohash_base91.clone(),
|
robohash_base91: trader_setup.robosats_robohash_base91.clone(),
|
||||||
amount_satoshi: amount,
|
amount_satoshi: amount,
|
||||||
order_type: trade_type.to_string(),
|
order_type: trade_type.to_string(),
|
||||||
bond_ratio: trader_setup.bond_ratio,
|
bond_ratio: trader_setup.bond_ratio,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch(trader_setup: &TraderSettings) -> Result<OfferCreationResponse> {
|
pub fn fetch(trader_setup: &TraderSettings) -> Result<OfferCreationResponse> {
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
let res = client.post(format!("{}{}", trader_setup.coordinator_endpoint, "/create-offer"))
|
let res = client
|
||||||
.json(&Self::_format_request(trader_setup))
|
.post(format!(
|
||||||
.send()?
|
"{}{}",
|
||||||
.json::<OfferCreationResponse>()?;
|
trader_setup.coordinator_endpoint, "/create-offer"
|
||||||
|
))
|
||||||
|
.json(&Self::_format_request(trader_setup))
|
||||||
|
.send()?
|
||||||
|
.json::<OfferCreationResponse>()?;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,26 +6,25 @@ mod wallet;
|
|||||||
|
|
||||||
use core::panic;
|
use core::panic;
|
||||||
|
|
||||||
use cli::CliSettings;
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use cli::CliSettings;
|
||||||
|
|
||||||
fn start_trade_pipeline(cli_input: &CliSettings) -> Result<()> {
|
fn start_trade_pipeline(cli_input: &CliSettings) -> Result<()> {
|
||||||
if let CliSettings::Maker(maker_data) = cli_input {
|
if let CliSettings::Maker(maker_data) = cli_input {
|
||||||
Ok(trading::run_maker(maker_data)?)
|
Ok(trading::run_maker(maker_data)?)
|
||||||
} else if let CliSettings::Taker(taker_data) = cli_input {
|
} else if let CliSettings::Taker(taker_data) = cli_input {
|
||||||
Err(anyhow!("not implemented!"))
|
// trading::run_taker(taker_data)?;
|
||||||
// trading::taker::run_taker(taker_data)?;
|
} else {
|
||||||
} else {
|
Err(anyhow!("Wrong mode selected!"))
|
||||||
Err(anyhow!("Wrong mode selected!"))
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
// env_logger::builder().filter_level(log::LevelFilter::Debug).init(); // enable to show extended BDK debug messages
|
// env_logger::builder().filter_level(log::LevelFilter::Debug).init(); // enable to show extended BDK debug messages
|
||||||
|
|
||||||
let mode = CliSettings::parse_cli_args()?;
|
let mode = CliSettings::parse_cli_args()?;
|
||||||
dbg!("CLI input :", &mode);
|
dbg!("CLI input :", &mode);
|
||||||
start_trade_pipeline(&mode)?;
|
start_trade_pipeline(&mode)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,33 +1,36 @@
|
|||||||
// use maker_utils;
|
// use maker_utils;
|
||||||
// use taker_utils;
|
// use taker_utils;
|
||||||
// use utils;
|
// mod utils;
|
||||||
|
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use bdk::bitcoin::block;
|
|
||||||
use crate::cli::TraderSettings;
|
use crate::cli::TraderSettings;
|
||||||
use crate::communication::api::OfferCreationResponse;
|
use crate::communication::api::OfferCreationResponse;
|
||||||
use crate::wallet::{load_wallet, bond::Bond};
|
use crate::wallet::{bond::Bond, load_wallet};
|
||||||
|
use anyhow::Result;
|
||||||
|
use bdk::bitcoin::block;
|
||||||
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
|
||||||
use bdk::electrum_client::Client;
|
use bdk::electrum_client::Client;
|
||||||
|
|
||||||
|
|
||||||
pub fn run_maker(maker_config: &TraderSettings) -> Result<()> {
|
pub fn run_maker(maker_config: &TraderSettings) -> Result<()> {
|
||||||
let client = Client::new(&maker_config.electrum_endpoint)?;
|
let blockchain = ElectrumBlockchain::from(Client::new(&maker_config.electrum_endpoint)?);
|
||||||
let blockchain = ElectrumBlockchain::from(client);
|
|
||||||
|
|
||||||
let offer_conditions = OfferCreationResponse::fetch(maker_config)?;
|
let offer_conditions = OfferCreationResponse::fetch(maker_config)?;
|
||||||
// let offer_conditions = OfferCreationResponse { // hardcoded for testing, locking_address is owned by .env xprv
|
// let offer_conditions = OfferCreationResponse { // hardcoded for testing, locking_address is owned by .env xprv
|
||||||
// locking_amount: 90000,
|
// locking_amount: 90000,
|
||||||
// bond_address: "tb1pfdvgfzwp8vhmelpv8w9kezz7nsmxw68jz6yehgze6mzx0t6r9t2qv9ynmm".to_string(),
|
// bond_address: "tb1pfdvgfzwp8vhmelpv8w9kezz7nsmxw68jz6yehgze6mzx0t6r9t2qv9ynmm".to_string(),
|
||||||
// };
|
// };
|
||||||
|
|
||||||
let wallet = load_wallet(maker_config, &blockchain)?; // initialize the wallet with xprv
|
let wallet = load_wallet(maker_config, &blockchain)?; // initialize the wallet with xprv
|
||||||
|
|
||||||
let bond = Bond::assemble(&wallet, &offer_conditions, maker_config)?; // assemble the Bond transaction for offer creation
|
let bond = Bond::assemble(&wallet, &offer_conditions, maker_config)?; // assemble the Bond transaction for offer creation
|
||||||
// blockchain.broadcast(&bond.extract_tx())?; // publish bond to be mined for testing
|
// blockchain.broadcast(&bond.extract_tx())?; // publish bond to be mined for testing
|
||||||
dbg!(&bond.extract_tx().txid());
|
dbg!(&bond.extract_tx().txid());
|
||||||
|
Ok(())
|
||||||
Ok(())
|
}
|
||||||
|
|
||||||
|
pub fn run_taker(taker_config: &TraderSettings) -> Result<()> {
|
||||||
|
let blockchain = ElectrumBlockchain::from(Client::new(&taker_config.electrum_endpoint)?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
|
||||||
@ -1,47 +1,55 @@
|
|||||||
use anyhow::{Result, anyhow};
|
use anyhow::{anyhow, Result};
|
||||||
|
use bdk::bitcoin::address::{NetworkChecked, NetworkUnchecked};
|
||||||
use bdk::bitcoin::psbt::PartiallySignedTransaction;
|
use bdk::bitcoin::psbt::PartiallySignedTransaction;
|
||||||
use bdk::bitcoin::ScriptBuf;
|
use bdk::bitcoin::ScriptBuf;
|
||||||
use bdk::{database::MemoryDatabase, wallet::coin_selection::BranchAndBoundCoinSelection, Wallet, SignOptions, FeeRate};
|
use bdk::bitcoin::{Address, Network};
|
||||||
|
use bdk::{
|
||||||
|
database::MemoryDatabase, wallet::coin_selection::BranchAndBoundCoinSelection, FeeRate,
|
||||||
|
SignOptions, Wallet,
|
||||||
|
};
|
||||||
use serde::de::value;
|
use serde::de::value;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use bdk::bitcoin::{Address, Network};
|
|
||||||
use bdk::bitcoin::address::{NetworkUnchecked, NetworkChecked};
|
|
||||||
|
|
||||||
use crate::communication::api::OfferCreationResponse;
|
use crate::communication::api::OfferCreationResponse;
|
||||||
use crate::wallet::TraderSettings;
|
use crate::wallet::TraderSettings;
|
||||||
|
|
||||||
pub struct Outpoint {
|
pub struct Outpoint {
|
||||||
pub txid_hex: String,
|
pub txid_hex: String,
|
||||||
pub index: u32
|
pub index: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Bond {
|
pub struct Bond {
|
||||||
pub signed_bond_tx_hex: String,
|
pub signed_bond_tx_hex: String,
|
||||||
pub used_outpoint: Outpoint
|
pub used_outpoint: Outpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bond {
|
impl Bond {
|
||||||
pub fn assemble(wallet: &Wallet<MemoryDatabase>,
|
pub fn assemble(
|
||||||
bond_target: &OfferCreationResponse,
|
wallet: &Wallet<MemoryDatabase>,
|
||||||
trader_input: &TraderSettings) -> Result<PartiallySignedTransaction> {
|
bond_target: &OfferCreationResponse,
|
||||||
|
trader_input: &TraderSettings,
|
||||||
|
) -> Result<PartiallySignedTransaction> {
|
||||||
// parse bond locking address as Address struct and verify network is testnet
|
// parse bond locking address as Address struct and verify network is testnet
|
||||||
let address: Address = Address::from_str(&bond_target.bond_address)?
|
let address: Address =
|
||||||
.require_network(Network::Testnet)?;
|
Address::from_str(&bond_target.bond_address)?.require_network(Network::Testnet)?;
|
||||||
|
|
||||||
// build bond locking transaction. Use coin selection to add at least enough outputs
|
// build bond locking transaction. Use coin selection to add at least enough outputs
|
||||||
// to have the full trading sum as change as evidence for the coordinator that the maker owns
|
// to have the full trading sum as change as evidence for the coordinator that the maker owns
|
||||||
// enough funds to cover the trade
|
// enough funds to cover the trade
|
||||||
let (mut psbt, details) = {
|
let (mut psbt, details) = {
|
||||||
let mut builder = wallet.build_tx()
|
let mut builder = wallet
|
||||||
.coin_selection(BranchAndBoundCoinSelection::new(trader_input.trade_type.value()));
|
.build_tx()
|
||||||
|
.coin_selection(BranchAndBoundCoinSelection::new(
|
||||||
|
trader_input.trade_type.value(),
|
||||||
|
));
|
||||||
|
|
||||||
builder
|
builder
|
||||||
.add_recipient(address.script_pubkey(), bond_target.locking_amount)
|
.add_recipient(address.script_pubkey(), bond_target.locking_amount)
|
||||||
.do_not_spend_change()
|
.do_not_spend_change()
|
||||||
.fee_rate(FeeRate::from_sat_per_vb(201.0));
|
.fee_rate(FeeRate::from_sat_per_vb(201.0));
|
||||||
|
|
||||||
builder.finish()?
|
builder.finish()?
|
||||||
};
|
};
|
||||||
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
||||||
if !finalized {
|
if !finalized {
|
||||||
return Err(anyhow!("Transaction could not be finalized"));
|
return Err(anyhow!("Transaction could not be finalized"));
|
||||||
|
|||||||
@ -1,21 +1,26 @@
|
|||||||
pub mod bond;
|
pub mod bond;
|
||||||
pub mod wallet_utils;
|
|
||||||
pub mod musig2;
|
pub mod musig2;
|
||||||
|
pub mod wallet_utils;
|
||||||
|
|
||||||
use bdk::{bitcoin, keys::DescriptorPublicKey, miniscript::Descriptor, template::{Bip86, DescriptorTemplate}, KeychainKind, SyncOptions, Wallet};
|
|
||||||
use bdk::database::MemoryDatabase;
|
|
||||||
use bdk::blockchain::ElectrumBlockchain;
|
|
||||||
use bdk::bitcoin::{Network, bip32::ExtendedPrivKey};
|
|
||||||
use bdk::electrum_client::Client;
|
|
||||||
use anyhow::Result;
|
|
||||||
use wallet_utils::get_seed;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use crate::cli::TraderSettings;
|
use crate::cli::TraderSettings;
|
||||||
|
use anyhow::Result;
|
||||||
|
use bdk::bitcoin::{bip32::ExtendedPrivKey, Network};
|
||||||
|
use bdk::blockchain::ElectrumBlockchain;
|
||||||
|
use bdk::database::MemoryDatabase;
|
||||||
|
use bdk::electrum_client::Client;
|
||||||
|
use bdk::{
|
||||||
|
bitcoin,
|
||||||
|
keys::DescriptorPublicKey,
|
||||||
|
miniscript::Descriptor,
|
||||||
|
template::{Bip86, DescriptorTemplate},
|
||||||
|
KeychainKind, SyncOptions, Wallet,
|
||||||
|
};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use wallet_utils::get_seed;
|
||||||
|
|
||||||
pub struct WalletDescriptors {
|
pub struct WalletDescriptors {
|
||||||
pub descriptor: Bip86<ExtendedPrivKey>,
|
pub descriptor: Bip86<ExtendedPrivKey>,
|
||||||
pub change_descriptor: Option<Bip86<ExtendedPrivKey>>
|
pub change_descriptor: Option<Bip86<ExtendedPrivKey>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_wallet_xprv(xprv_input: Option<String>) -> Result<ExtendedPrivKey> {
|
pub fn get_wallet_xprv(xprv_input: Option<String>) -> Result<ExtendedPrivKey> {
|
||||||
@ -32,15 +37,21 @@ pub fn get_wallet_xprv(xprv_input: Option<String>) -> Result<ExtendedPrivKey> {
|
|||||||
Ok(xprv)
|
Ok(xprv)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_wallet(trader_config: &TraderSettings, blockchain: &ElectrumBlockchain) -> Result<Wallet<MemoryDatabase>> {
|
pub fn load_wallet(
|
||||||
|
trader_config: &TraderSettings,
|
||||||
|
blockchain: &ElectrumBlockchain,
|
||||||
|
) -> Result<Wallet<MemoryDatabase>> {
|
||||||
let wallet = Wallet::new(
|
let wallet = Wallet::new(
|
||||||
Bip86(trader_config.wallet_xprv.clone(), KeychainKind::External),
|
Bip86(trader_config.wallet_xprv.clone(), KeychainKind::External),
|
||||||
Some(Bip86(trader_config.wallet_xprv.clone(), KeychainKind::Internal)),
|
Some(Bip86(
|
||||||
bitcoin::Network::Testnet,
|
trader_config.wallet_xprv.clone(),
|
||||||
MemoryDatabase::default(), // non-permanent storage
|
KeychainKind::Internal,
|
||||||
)?;
|
)),
|
||||||
|
bitcoin::Network::Testnet,
|
||||||
|
MemoryDatabase::default(), // non-permanent storage
|
||||||
|
)?;
|
||||||
|
|
||||||
wallet.sync(blockchain, SyncOptions::default())?;
|
wallet.sync(blockchain, SyncOptions::default())?;
|
||||||
println!("Descriptor balance: {} SAT", wallet.get_balance()?);
|
println!("Descriptor balance: {} SAT", wallet.get_balance()?);
|
||||||
Ok(wallet)
|
Ok(wallet)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use musig2::{AggNonce, FirstRound, PartialSignature, PubNonce, SecNonceSpices, SecondRound};
|
use musig2::{AggNonce, FirstRound, PartialSignature, PubNonce, SecNonceSpices, SecondRound};
|
||||||
|
|
||||||
// https://docs.rs/musig2/latest/musig2/
|
// https://docs.rs/musig2/latest/musig2/
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use rand_core::{RngCore, OsRng};
|
use rand_core::{OsRng, RngCore};
|
||||||
|
|
||||||
// uses operating system rng which is secure for cryptography
|
// uses operating system rng which is secure for cryptography
|
||||||
pub fn get_seed() -> [u8; 32] {
|
pub fn get_seed() -> [u8; 32] {
|
||||||
|
|||||||
7
taptrade-cli-demo/trader/taker.env
Normal file
7
taptrade-cli-demo/trader/taker.env
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
ELECTRUM_ENDPOINT="ssl://mempool.space:40002" # testnet 4 electrum server
|
||||||
|
COORDINATOR_ENDPOINT="http://127.0.0.1:3000"
|
||||||
|
ROBOHASH_BASE91="n!DyWmzXG3"1"*$S(GH<r+!G/!!u^aFTe%!x(iee}R[+vv+S4t^a<mAk!I{ybCQiD2%Idj>G4RB2gvM" # "base91 of hex of sha256 of robot22"
|
||||||
|
TRADE_TYPE="sell"
|
||||||
|
PAYOUT_ADDRESS="tb1p37qg73t5y0l4un3q5dknzl8fgfhemghaap67wns45pzgrw2tasrq6kesxm"
|
||||||
|
BOND_RATIO=5
|
||||||
|
XPRV="tprv8ZgxMBicQKsPdHuCSjhQuSZP1h6ZTeiRqREYS5guGPdtL7D1uNLpnJmb2oJep99Esq1NbNZKVJBNnD2ZhuXSK7G5eFmmcx73gsoa65e2U32" # wallet xprv
|
||||||
Reference in New Issue
Block a user