This commit is contained in:
Felix
2024-06-11 14:05:16 +00:00
parent a5f3b4bb58
commit 125bf4ddcf
14 changed files with 284 additions and 245 deletions

View File

@ -0,0 +1 @@
hard_tabs = true

View File

@ -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

View File

@ -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

View File

@ -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};

View File

@ -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,
} }

View File

@ -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)
} }
} }

View File

@ -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(())
} }

View File

@ -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(())
} }

View File

@ -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"));

View File

@ -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)
} }

View File

@ -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/

View File

@ -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] {

View 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