Merge branch 'research' of https://github.com/RoboSats/taptrade-core into research
merge
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 436 KiB After Width: | Height: | Size: 436 KiB |
Before Width: | Height: | Size: 436 KiB After Width: | Height: | Size: 436 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
@ -34,3 +34,7 @@ chrono = "0.4.38"
|
||||
lto = true
|
||||
opt-level = 3
|
||||
strip = true
|
||||
|
||||
[lib]
|
||||
name = "coordinator"
|
||||
path = "src/main.rs"
|
@ -1,3 +1,77 @@
|
||||
/// This module contains the API structures used for communication in the coordinator.
|
||||
///
|
||||
/// The `OfferRequest` struct represents a request to create an offer. It contains the following fields:
|
||||
/// - `robohash_hex`: The identifier of the trader.
|
||||
/// - `amount_satoshi`: The amount in satoshi to buy or sell.
|
||||
/// - `is_buy_order`: A boolean indicating whether it is a buy order or a sell order.
|
||||
/// - `bond_ratio`: The percentage of the trading amount to be used as a bond.
|
||||
/// - `offer_duration_ts`: The unix timestamp indicating how long the offer should stay available.
|
||||
///
|
||||
/// The `BondRequirementResponse` struct represents the response containing bond requirements. It has the following fields:
|
||||
/// - `bond_address`: The bond address.
|
||||
/// - `locking_amount_sat`: The minimum amount of the bond output in satoshi.
|
||||
///
|
||||
/// The `BondSubmissionRequest` struct represents a request to submit a bond. It contains the following fields:
|
||||
/// - `robohash_hex`: The identifier of the trader.
|
||||
/// - `signed_bond_hex`: The signed bond transaction in hex format.
|
||||
/// - `payout_address`: The payout address.
|
||||
/// - `taproot_pubkey_hex`: The taproot public key in hex format.
|
||||
/// - `musig_pub_nonce_hex`: The musig public nonce in hex format.
|
||||
/// - `musig_pubkey_hex`: The musig public key in hex format.
|
||||
/// - `bdk_psbt_inputs_hex_csv`: The bdk psbt inputs in hex format.
|
||||
/// - `client_change_address`: The client change address.
|
||||
///
|
||||
/// The `OfferActivatedResponse` struct represents the response after successfully activating an offer. It has the following fields:
|
||||
/// - `offer_id_hex`: The offer ID in hex format.
|
||||
/// - `bond_locked_until_timestamp`: The unix timestamp until which the bond should not be touched unless the offer gets taken.
|
||||
///
|
||||
/// The `OffersRequest` struct represents a request to get offers. It contains the following fields:
|
||||
/// - `buy_offers`: A boolean indicating whether to look for buy offers or sell offers.
|
||||
/// - `amount_min_sat`: The minimum amount in satoshi.
|
||||
/// - `amount_max_sat`: The maximum amount in satoshi.
|
||||
///
|
||||
/// The `PublicOffer` struct represents information about a public offer. It has the following fields:
|
||||
/// - `amount_sat`: The amount in satoshi.
|
||||
/// - `offer_id_hex`: The offer ID in hex format.
|
||||
/// - `required_bond_amount_sat`: The required bond amount in satoshi.
|
||||
/// - `bond_locking_address`: The bond locking address.
|
||||
///
|
||||
/// The `PublicOffers` struct represents a collection of public offers. It has the following field:
|
||||
/// - `offers`: An optional vector of `PublicOffer` structs. This field is not included in the return JSON if no offers are available.
|
||||
///
|
||||
/// The `OfferTakenResponse` struct represents the response after taking an offer. It has the following fields:
|
||||
/// - `escrow_psbt_hex`: The escrow PSBT in hex format.
|
||||
/// - `escrow_output_descriptor`: The escrow output descriptor.
|
||||
/// - `escrow_amount_maker_sat`: The escrow amount for the maker in satoshi.
|
||||
/// - `escrow_amount_taker_sat`: The escrow amount for the taker in satoshi.
|
||||
/// - `escrow_fee_sat_per_participant`: The escrow fee in satoshi per participant.
|
||||
///
|
||||
/// The `OfferPsbtRequest` struct represents a request to receive the escrow PSBT for a specified offer. It contains the following fields:
|
||||
/// - `offer`: The `PublicOffer` struct representing the offer.
|
||||
/// - `trade_data`: The `BondSubmissionRequest` struct representing the trade data.
|
||||
///
|
||||
/// The `OfferTakenRequest` struct represents a request to take an offer. It contains the following fields:
|
||||
/// - `robohash_hex`: The identifier of the trader.
|
||||
/// - `offer_id_hex`: The offer ID in hex format.
|
||||
///
|
||||
/// The `PsbtSubmissionRequest` struct represents a request to submit a PSBT. It contains the following fields:
|
||||
/// - `signed_psbt_hex`: The signed PSBT in hex format.
|
||||
/// - `offer_id_hex`: The offer ID in hex format.
|
||||
/// - `robohash_hex`: The identifier of the trader.
|
||||
///
|
||||
/// The `PayoutResponse` struct represents the response after a payout. It has the following fields:
|
||||
/// - `payout_psbt_hex`: The payout PSBT in hex format.
|
||||
/// - `agg_musig_nonce_hex`: The aggregated musig nonce in hex format.
|
||||
/// - `agg_musig_pubkey_ctx_hex`: The aggregated musig public key context in hex format.
|
||||
///
|
||||
/// The `TradeObligationsUnsatisfied` struct represents unsatisfied trade obligations. It has the following fields:
|
||||
/// - `robohash_hex`: The identifier of the trader.
|
||||
/// - `offer_id_hex`: The offer ID in hex format.
|
||||
///
|
||||
/// The `PayoutSignatureRequest` struct represents a request for a payout signature. It contains the following fields:
|
||||
/// - `partial_sig_hex`: The partial signature in hex format.
|
||||
/// - `offer_id_hex`: The offer ID in hex format.
|
||||
/// - `robohash_hex`: The identifier of the trader.
|
||||
use super::*;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Validate)]
|
||||
|
@ -1,5 +1,51 @@
|
||||
use super::*;
|
||||
|
||||
/// Validates the timestamp of an offer duration.
|
||||
///
|
||||
/// This function takes an offer duration timestamp as input and validates it against the current time.
|
||||
/// It checks if the offer duration is within a valid range, which is between the current time plus 3 hours
|
||||
/// and the current time plus 7 days. If the offer duration is too short or too long, it returns a validation error.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `offer_duration_ts` - The offer duration timestamp to validate.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Result<(), ValidationError>` - An empty result if the offer duration is valid, or a validation error if it is not.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use coordinator::communication::communication_utils::validate_timestamp;
|
||||
/// use std::time::{SystemTime, UNIX_EPOCH};
|
||||
///
|
||||
/// // Get the current time
|
||||
/// let now = SystemTime::now();
|
||||
/// // Convert the current time to a UNIX timestamp
|
||||
/// let unix_timestamp = now
|
||||
/// .duration_since(UNIX_EPOCH)
|
||||
/// .expect("Time went backwards")
|
||||
/// .as_secs();
|
||||
///
|
||||
/// // Use a timestamp that is within the valid range (current time + 4 hours)
|
||||
/// let offer_duration_ts = unix_timestamp + 4 * 3600;
|
||||
/// let result = validate_timestamp(offer_duration_ts);
|
||||
/// assert!(result.is_ok());
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function can return the following errors:
|
||||
///
|
||||
/// * `ValidationError` - If the offer duration is too short or too long.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function may panic if the system time goes backwards during the calculation of the current time.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is safe to use.
|
||||
pub fn validate_timestamp(offer_duration_ts: u64) -> Result<(), ValidationError> {
|
||||
// Get the current time
|
||||
let now = SystemTime::now();
|
||||
|
@ -372,7 +372,7 @@ async fn test_fetch_taker_bond_requirements() -> Result<()> {
|
||||
|
||||
// Call the fetch_taker_bond_requirements function
|
||||
let result = database
|
||||
.fetch_taker_bond_requirements(&offer_id_hex.to_string())
|
||||
.fetch_taker_bond_requirements(offer_id_hex)
|
||||
.await?;
|
||||
|
||||
// Verify the result
|
||||
|
@ -1,7 +1,7 @@
|
||||
mod communication;
|
||||
mod coordinator;
|
||||
mod database;
|
||||
mod wallet;
|
||||
pub mod communication;
|
||||
pub mod coordinator;
|
||||
pub mod database;
|
||||
pub mod wallet;
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use axum::{
|
||||
|
@ -12,7 +12,6 @@ use bdk::{
|
||||
Wallet,
|
||||
};
|
||||
use bitcoin;
|
||||
use bitcoin::consensus::Decodable;
|
||||
|
||||
fn get_backend() -> RpcBlockchain {
|
||||
dotenv().ok();
|
||||
@ -152,7 +151,7 @@ async fn test_transaction_without_signature() {
|
||||
};
|
||||
|
||||
let result = test_wallet
|
||||
.validate_bond_tx_hex(&bond_without_signature, &requirements)
|
||||
.validate_bond_tx_hex(bond_without_signature, &requirements)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
test_wallet.shutdown().await;
|
||||
@ -170,7 +169,7 @@ async fn test_transaction_with_invalid_signature() {
|
||||
};
|
||||
|
||||
let result = test_wallet
|
||||
.validate_bond_tx_hex(&bond_with_invalid_signature, &requirements)
|
||||
.validate_bond_tx_hex(bond_with_invalid_signature, &requirements)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
test_wallet.shutdown().await;
|
||||
@ -187,7 +186,7 @@ async fn test_bond_with_spent_input() {
|
||||
};
|
||||
|
||||
let result = test_wallet
|
||||
.validate_bond_tx_hex(&bond_with_spent_input, &requirements)
|
||||
.validate_bond_tx_hex(bond_with_spent_input, &requirements)
|
||||
.await;
|
||||
assert!(result.is_err());
|
||||
test_wallet.shutdown().await;
|
||||
@ -203,7 +202,7 @@ async fn test_valid_bond_tx() {
|
||||
bond_address: "tb1p5yh969z6fgatg0mvcyvggd08fujnat8890vcdud277q06rr9xgmqwfdkcx".to_string(),
|
||||
};
|
||||
|
||||
let result = test_wallet.validate_bond_tx_hex(&bond, &requirements).await;
|
||||
let result = test_wallet.validate_bond_tx_hex(bond, &requirements).await;
|
||||
assert!(result.is_ok());
|
||||
test_wallet.shutdown().await;
|
||||
}
|
||||
@ -218,7 +217,7 @@ async fn test_invalid_bond_tx_low_input_sum() {
|
||||
bond_address: "tb1p5yh969z6fgatg0mvcyvggd08fujnat8890vcdud277q06rr9xgmqwfdkcx".to_string(),
|
||||
};
|
||||
|
||||
let result = test_wallet.validate_bond_tx_hex(&bond, &requirements).await;
|
||||
let result = test_wallet.validate_bond_tx_hex(bond, &requirements).await;
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
@ -237,7 +236,7 @@ async fn test_invalid_bond_tx_low_output_sum() {
|
||||
bond_address: "tb1p5yh969z6fgatg0mvcyvggd08fujnat8890vcdud277q06rr9xgmqwfdkcx".to_string(),
|
||||
};
|
||||
|
||||
let result = test_wallet.validate_bond_tx_hex(&bond, &requirements).await;
|
||||
let result = test_wallet.validate_bond_tx_hex(bond, &requirements).await;
|
||||
test_wallet.shutdown().await;
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
@ -256,7 +255,7 @@ async fn test_invalid_bond_tx_low_fee_rate() {
|
||||
bond_address: "tb1p5yh969z6fgatg0mvcyvggd08fujnat8890vcdud277q06rr9xgmqwfdkcx".to_string(),
|
||||
};
|
||||
|
||||
let result = test_wallet.validate_bond_tx_hex(&bond, &requirements).await;
|
||||
let result = test_wallet.validate_bond_tx_hex(bond, &requirements).await;
|
||||
test_wallet.shutdown().await;
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
@ -383,7 +382,7 @@ fn test_create_escrow_spending_psbt() {
|
||||
|
||||
let escrow_utxo = escrow_output_wallet.list_unspent().unwrap();
|
||||
dbg!(&escrow_utxo);
|
||||
assert!(escrow_utxo.len() > 0);
|
||||
assert!(!escrow_utxo.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -28,7 +28,7 @@ impl PublicOffers {
|
||||
}
|
||||
};
|
||||
if res.status() == 204 {
|
||||
return Ok(PublicOffers { offers: None });
|
||||
Ok(PublicOffers { offers: None })
|
||||
} else {
|
||||
match res.json::<PublicOffers>() {
|
||||
Ok(offers) => {
|
||||
|
@ -29,7 +29,7 @@ impl ActiveOffer {
|
||||
payout_address: payout_address.address.to_string(),
|
||||
musig_pub_nonce_hex: hex::encode(musig_data.nonce.get_pub_for_sharing()?.serialize()),
|
||||
musig_pubkey_hex: hex::encode(musig_data.public_key.serialize()),
|
||||
taproot_pubkey_hex: hex::encode(&trading_wallet.taproot_pubkey.serialize()),
|
||||
taproot_pubkey_hex: hex::encode(trading_wallet.taproot_pubkey.serialize()),
|
||||
bdk_psbt_inputs_hex_csv: psbt_inputs_hex_csv.clone(),
|
||||
client_change_address: escrow_change_address.clone(),
|
||||
};
|
||||
|
@ -65,7 +65,7 @@ pub fn run_maker(maker_config: &TraderSettings) -> Result<()> {
|
||||
offer.used_musig_config,
|
||||
)?;
|
||||
// submit signed payout psbt back to coordinator
|
||||
PayoutSignatureRequest::send(&maker_config, &signature, &offer.offer_id_hex)?;
|
||||
PayoutSignatureRequest::send(maker_config, &signature, &offer.offer_id_hex)?;
|
||||
} else {
|
||||
warn!("Trader unsatisfied. Initiating escrow mode.");
|
||||
TradeObligationsUnsatisfied::request_escrow(&offer.offer_id_hex, maker_config)?;
|
||||
@ -108,7 +108,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)?;
|
||||
PayoutSignatureRequest::send(taker_config, &signature, &accepted_offer.offer_id_hex)?;
|
||||
// here we need to handle if the other party is not cooperating
|
||||
} else {
|
||||
error!("Trader unsatisfied. Initiating escrow mode.");
|
||||
|
@ -251,7 +251,7 @@ impl TradingWallet {
|
||||
match keyspend_sig {
|
||||
MaybeScalar::Valid(s) => Ok(s.encode_hex()),
|
||||
MaybeScalar::Zero => {
|
||||
return Err(anyhow!("keyspend sig maybe scalar is Zero"));
|
||||
Err(anyhow!("keyspend sig maybe scalar is Zero"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|