mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-12-16 00:53:58 -05:00
feat(wallet): Use mempool histogram for fee estimation (#358)
* feat(wallet): Use mempool.space as a secondary fee estimation source * fix: warn if mempool client cannot be instantiated * make clippy happy * nitpick: rename clippy_check to clippy in justfile * rename `estimate_fee_rate_from_mempool` to `estimate_fee_rate_from_histogram` for clarity * dprint fmt * make clippy happy * change teacing level back to debug! * change log levels * refactors * refactor: estimate_fee and min_relay_fee * serde camel case Co-authored-by: Byron Hambly <byron@hambly.dev> * refactors * Add comments, use Weight struct where possible * fmt, fix testrs * dont fallback to bitcoin::MAX, fail instead * make mempool space optional * fmt * refactor: use estimate_fee(...) in max_giveable(...) * refactor max_giveable(...) * refactor max_giveeable to return fee as well, remove safety margin for fee * fix compile * fmtr * fix(integration test): Use pre-calculated cancel / punish fees for assert_alice_punished * fix(integration test): Use real fees for asserts * sync wallet before transaction_fee call * split send_to_address into sweep_balance_to_address_dynamic_fee --------- Co-authored-by: Byron Hambly <byron@hambly.dev>
This commit is contained in:
parent
854b14939e
commit
091ba57547
23 changed files with 995 additions and 358 deletions
|
|
@ -5,3 +5,6 @@
|
|||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
cc 849f8b01f49fc9a913100203698a9151d8de8a37564e1d3b1e3b4169e192f58a # shrinks to funding_amount = 290250686, num_utxos = 3, sats_per_vb = 75.35638, key = ExtendedPrivKey { network: Regtest, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: [private key data], chain_code: 0b7a29ca6990bbc9b9187c1d1a07e2cf68e32f5ce55d2df01edf8a4ac2ee2a4b }, alice = Point<Normal,Public,NonZero>(0299a8c6a662e2e9e8ee7c6889b75a51c432812b4bf70c1d76eace63abc1bdfb1b), bob = Point<Normal,Public,NonZero>(027165b1f9924030c90d38c511da0f4397766078687997ed34d6ef2743d2a7bbed)
|
||||
cc ec2c53bbf967d46a4e9a394da96f8089ea77dcca794dec9fcc13c5a7141eb929 # shrinks to funding_amount = 271331, num_utxos = 4, sats_per_vb = 308, key = Xpriv { network: Test, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: SecretKey(#dc659542f09c329f), chain_code: c94a49bbee951f7f7401c801db73c23cf17b0b3aa2f6246a8616fe2205a6ca51 }, alice = Point<Normal,Public,NonZero>(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798), bob = Point<Normal,Public,NonZero>(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)
|
||||
cc 2062fe113880e48af4d170fca9a2690e57a55f8985f86a66bd53e8bbe5c62dbd # shrinks to funding_amount = 3000, num_utxos = 1, sats_per_vb = 7, key = Xpriv { network: Test, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: SecretKey(#dc659542f09c329f), chain_code: c94a49bbee951f7f7401c801db73c23cf17b0b3aa2f6246a8616fe2205a6ca51 }, alice = Point<Normal,Public,NonZero>(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798), bob = Point<Normal,Public,NonZero>(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)
|
||||
cc 381f4c0a72579c36107b0fd7a5b9c8aad2f02c0f9908b3a137b71ff05e7d8d33 # shrinks to funding_amount = 3000, num_utxos = 1, sats_per_vb = 7, key = Xpriv { network: Test, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: SecretKey(#dc659542f09c329f), chain_code: c94a49bbee951f7f7401c801db73c23cf17b0b3aa2f6246a8616fe2205a6ca51 }, alice = Point<Normal,Public,NonZero>(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798), bob = Point<Normal,Public,NonZero>(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)
|
||||
|
|
|
|||
|
|
@ -185,6 +185,12 @@ pub struct Bitcoin {
|
|||
pub finality_confirmations: Option<u32>,
|
||||
#[serde(with = "crate::bitcoin::network")]
|
||||
pub network: bitcoin::Network,
|
||||
#[serde(default = "default_use_mempool_space_fee_estimation")]
|
||||
pub use_mempool_space_fee_estimation: bool,
|
||||
}
|
||||
|
||||
fn default_use_mempool_space_fee_estimation() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
|
|
@ -377,6 +383,7 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
|
|||
target_block,
|
||||
finality_confirmations: None,
|
||||
network: bitcoin_network,
|
||||
use_mempool_space_fee_estimation: true,
|
||||
},
|
||||
monero: Monero {
|
||||
wallet_rpc_url: monero_wallet_rpc_url,
|
||||
|
|
@ -421,6 +428,7 @@ mod tests {
|
|||
target_block: defaults.bitcoin_confirmation_target,
|
||||
finality_confirmations: None,
|
||||
network: bitcoin::Network::Testnet,
|
||||
use_mempool_space_fee_estimation: true,
|
||||
},
|
||||
network: Network {
|
||||
listen: vec![defaults.listen_address_tcp],
|
||||
|
|
@ -465,6 +473,7 @@ mod tests {
|
|||
target_block: defaults.bitcoin_confirmation_target,
|
||||
finality_confirmations: None,
|
||||
network: bitcoin::Network::Bitcoin,
|
||||
use_mempool_space_fee_estimation: true,
|
||||
},
|
||||
network: Network {
|
||||
listen: vec![defaults.listen_address_tcp],
|
||||
|
|
@ -519,6 +528,7 @@ mod tests {
|
|||
target_block: defaults.bitcoin_confirmation_target,
|
||||
finality_confirmations: None,
|
||||
network: bitcoin::Network::Bitcoin,
|
||||
use_mempool_space_fee_estimation: true,
|
||||
},
|
||||
network: Network {
|
||||
listen,
|
||||
|
|
|
|||
|
|
@ -310,19 +310,22 @@ pub async fn main() -> Result<()> {
|
|||
Command::WithdrawBtc { amount, address } => {
|
||||
let bitcoin_wallet = init_bitcoin_wallet(&config, &seed, env_config).await?;
|
||||
|
||||
let amount = match amount {
|
||||
Some(amount) => amount,
|
||||
let withdraw_tx_unsigned = match amount {
|
||||
Some(amount) => {
|
||||
bitcoin_wallet
|
||||
.send_to_address_dynamic_fee(address, amount, None)
|
||||
.await?
|
||||
}
|
||||
None => {
|
||||
bitcoin_wallet
|
||||
.max_giveable(address.script_pubkey().len())
|
||||
.sweep_balance_to_address_dynamic_fee(address)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
let psbt = bitcoin_wallet
|
||||
.send_to_address(address, amount, None)
|
||||
let signed_tx = bitcoin_wallet
|
||||
.sign_and_finalize(withdraw_tx_unsigned)
|
||||
.await?;
|
||||
let signed_tx = bitcoin_wallet.sign_and_finalize(psbt).await?;
|
||||
|
||||
bitcoin_wallet.broadcast(signed_tx, "withdraw").await?;
|
||||
}
|
||||
|
|
@ -420,6 +423,7 @@ async fn init_bitcoin_wallet(
|
|||
})
|
||||
.finality_confirmations(env_config.bitcoin_finality_confirmations)
|
||||
.target_block(config.bitcoin.target_block)
|
||||
.use_mempool_space_fee_estimation(config.bitcoin.use_mempool_space_fee_estimation)
|
||||
.sync_interval(env_config.bitcoin_sync_interval())
|
||||
.build()
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -51,8 +51,8 @@ mod tests {
|
|||
async fn given_no_balance_and_transfers_less_than_max_swaps_max_giveable() {
|
||||
let writer = capture_logs(LevelFilter::INFO);
|
||||
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
|
||||
Amount::ZERO,
|
||||
Amount::from_btc(0.0009).unwrap(),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::from_btc(0.0009).unwrap(), Amount::from_sat(1000)),
|
||||
])));
|
||||
|
||||
let (amount, fees) = determine_btc_to_swap(
|
||||
|
|
@ -65,7 +65,6 @@ mod tests {
|
|||
result.give()
|
||||
},
|
||||
|| async { Ok(()) },
|
||||
|_| async { Ok(Amount::from_sat(1000)) },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
|
@ -78,10 +77,10 @@ mod tests {
|
|||
assert_eq!((amount, fees), (expected_amount, expected_fees));
|
||||
assert_eq!(
|
||||
writer.captured(),
|
||||
r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC
|
||||
INFO swap::api::request: Deposit at least 0.00001 BTC to cover the min quantity with fee!
|
||||
INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001 BTC max_giveable=0 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC
|
||||
INFO swap::api::request: Received Bitcoin new_balance=0.001 BTC max_giveable=0.0009 BTC
|
||||
r" INFO swap::cli::api::request: Received quote price=0.00100000 BTC minimum_amount=0 BTC maximum_amount=0.01000000 BTC
|
||||
INFO swap::cli::api::request: Deposit at least 0.00001000 BTC to cover the min quantity with fee!
|
||||
INFO swap::cli::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit_until_swap_will_start=0.00001000 BTC max_deposit_until_maximum_amount_is_reached=0.01001000 BTC max_giveable=0 BTC minimum_amount=0 BTC maximum_amount=0.01000000 BTC min_bitcoin_lock_tx_fee=0.00001000 BTC price=0.00100000 BTC
|
||||
INFO swap::cli::api::request: Received Bitcoin new_balance=0.00100000 BTC max_giveable=0.00090000 BTC
|
||||
"
|
||||
);
|
||||
}
|
||||
|
|
@ -90,8 +89,8 @@ mod tests {
|
|||
async fn given_no_balance_and_transfers_more_then_swaps_max_quantity_from_quote() {
|
||||
let writer = capture_logs(LevelFilter::INFO);
|
||||
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
|
||||
Amount::ZERO,
|
||||
Amount::from_btc(0.1).unwrap(),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::from_btc(0.1).unwrap(), Amount::from_sat(1000)),
|
||||
])));
|
||||
|
||||
let (amount, fees) = determine_btc_to_swap(
|
||||
|
|
@ -104,7 +103,6 @@ mod tests {
|
|||
result.give()
|
||||
},
|
||||
|| async { Ok(()) },
|
||||
|_| async { Ok(Amount::from_sat(1000)) },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
|
@ -117,10 +115,10 @@ mod tests {
|
|||
assert_eq!((amount, fees), (expected_amount, expected_fees));
|
||||
assert_eq!(
|
||||
writer.captured(),
|
||||
r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC
|
||||
INFO swap::api::request: Deposit at least 0.00001 BTC to cover the min quantity with fee!
|
||||
INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00001 BTC max_giveable=0 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC
|
||||
INFO swap::api::request: Received Bitcoin new_balance=0.1001 BTC max_giveable=0.1 BTC
|
||||
r" INFO swap::cli::api::request: Received quote price=0.00100000 BTC minimum_amount=0 BTC maximum_amount=0.01000000 BTC
|
||||
INFO swap::cli::api::request: Deposit at least 0.00001000 BTC to cover the min quantity with fee!
|
||||
INFO swap::cli::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit_until_swap_will_start=0.00001000 BTC max_deposit_until_maximum_amount_is_reached=0.01001000 BTC max_giveable=0 BTC minimum_amount=0 BTC maximum_amount=0.01000000 BTC min_bitcoin_lock_tx_fee=0.00001000 BTC price=0.00100000 BTC
|
||||
INFO swap::cli::api::request: Received Bitcoin new_balance=0.10010000 BTC max_giveable=0.10000000 BTC
|
||||
"
|
||||
);
|
||||
}
|
||||
|
|
@ -129,8 +127,8 @@ mod tests {
|
|||
async fn given_initial_balance_below_max_quantity_swaps_max_giveable() {
|
||||
let writer = capture_logs(LevelFilter::INFO);
|
||||
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
|
||||
Amount::from_btc(0.0049).unwrap(),
|
||||
Amount::from_btc(99.9).unwrap(),
|
||||
(Amount::from_btc(0.0049).unwrap(), Amount::from_sat(1000)),
|
||||
(Amount::from_btc(99.9).unwrap(), Amount::from_sat(1000)),
|
||||
])));
|
||||
|
||||
let (amount, fees) = determine_btc_to_swap(
|
||||
|
|
@ -143,7 +141,6 @@ mod tests {
|
|||
result.give()
|
||||
},
|
||||
|| async { Ok(()) },
|
||||
|_| async { Ok(Amount::from_sat(1000)) },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
|
@ -156,7 +153,7 @@ mod tests {
|
|||
assert_eq!((amount, fees), (expected_amount, expected_fees));
|
||||
assert_eq!(
|
||||
writer.captured(),
|
||||
" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC\n"
|
||||
" INFO swap::cli::api::request: Received quote price=0.00100000 BTC minimum_amount=0 BTC maximum_amount=0.01000000 BTC\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -164,8 +161,8 @@ mod tests {
|
|||
async fn given_initial_balance_above_max_quantity_swaps_max_quantity() {
|
||||
let writer = capture_logs(LevelFilter::INFO);
|
||||
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
|
||||
Amount::from_btc(0.1).unwrap(),
|
||||
Amount::from_btc(99.9).unwrap(),
|
||||
(Amount::from_btc(0.1).unwrap(), Amount::from_sat(1000)),
|
||||
(Amount::from_btc(99.9).unwrap(), Amount::from_sat(1000)),
|
||||
])));
|
||||
|
||||
let (amount, fees) = determine_btc_to_swap(
|
||||
|
|
@ -178,7 +175,6 @@ mod tests {
|
|||
result.give()
|
||||
},
|
||||
|| async { Ok(()) },
|
||||
|_| async { Ok(Amount::from_sat(1000)) },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
|
@ -191,7 +187,7 @@ mod tests {
|
|||
assert_eq!((amount, fees), (expected_amount, expected_fees));
|
||||
assert_eq!(
|
||||
writer.captured(),
|
||||
" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0 BTC maximum_amount=0.01 BTC\n"
|
||||
" INFO swap::cli::api::request: Received quote price=0.00100000 BTC minimum_amount=0 BTC maximum_amount=0.01000000 BTC\n"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -199,8 +195,8 @@ mod tests {
|
|||
async fn given_no_initial_balance_then_min_wait_for_sufficient_deposit() {
|
||||
let writer = capture_logs(LevelFilter::INFO);
|
||||
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
|
||||
Amount::ZERO,
|
||||
Amount::from_btc(0.01).unwrap(),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::from_btc(0.01).unwrap(), Amount::from_sat(1000)),
|
||||
])));
|
||||
|
||||
let (amount, fees) = determine_btc_to_swap(
|
||||
|
|
@ -213,7 +209,6 @@ mod tests {
|
|||
result.give()
|
||||
},
|
||||
|| async { Ok(()) },
|
||||
|_| async { Ok(Amount::from_sat(1000)) },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
|
@ -226,10 +221,10 @@ mod tests {
|
|||
assert_eq!((amount, fees), (expected_amount, expected_fees));
|
||||
assert_eq!(
|
||||
writer.captured(),
|
||||
r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC
|
||||
INFO swap::api::request: Deposit at least 0.01001 BTC to cover the min quantity with fee!
|
||||
INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.01001 BTC max_giveable=0 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC
|
||||
INFO swap::api::request: Received Bitcoin new_balance=0.0101 BTC max_giveable=0.01 BTC
|
||||
r" INFO swap::cli::api::request: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=21000000 BTC
|
||||
INFO swap::cli::api::request: Deposit at least 0.01001000 BTC to cover the min quantity with fee!
|
||||
INFO swap::cli::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit_until_swap_will_start=0.01001000 BTC max_deposit_until_maximum_amount_is_reached=21000000.00001000 BTC max_giveable=0 BTC minimum_amount=0.01000000 BTC maximum_amount=21000000 BTC min_bitcoin_lock_tx_fee=0.00001000 BTC price=0.00100000 BTC
|
||||
INFO swap::cli::api::request: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC
|
||||
"
|
||||
);
|
||||
}
|
||||
|
|
@ -238,8 +233,8 @@ mod tests {
|
|||
async fn given_balance_less_then_min_wait_for_sufficient_deposit() {
|
||||
let writer = capture_logs(LevelFilter::INFO);
|
||||
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
|
||||
Amount::from_btc(0.0001).unwrap(),
|
||||
Amount::from_btc(0.01).unwrap(),
|
||||
(Amount::from_btc(0.0001).unwrap(), Amount::from_sat(1000)),
|
||||
(Amount::from_btc(0.01).unwrap(), Amount::from_sat(1000)),
|
||||
])));
|
||||
|
||||
let (amount, fees) = determine_btc_to_swap(
|
||||
|
|
@ -252,7 +247,6 @@ mod tests {
|
|||
result.give()
|
||||
},
|
||||
|| async { Ok(()) },
|
||||
|_| async { Ok(Amount::from_sat(1000)) },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
|
@ -265,10 +259,10 @@ mod tests {
|
|||
assert_eq!((amount, fees), (expected_amount, expected_fees));
|
||||
assert_eq!(
|
||||
writer.captured(),
|
||||
r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC
|
||||
INFO swap::api::request: Deposit at least 0.00991 BTC to cover the min quantity with fee!
|
||||
INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.00991 BTC max_giveable=0.0001 BTC minimum_amount=0.01 BTC maximum_amount=184467440737.09551615 BTC
|
||||
INFO swap::api::request: Received Bitcoin new_balance=0.0101 BTC max_giveable=0.01 BTC
|
||||
r" INFO swap::cli::api::request: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=21000000 BTC
|
||||
INFO swap::cli::api::request: Deposit at least 0.00991000 BTC to cover the min quantity with fee!
|
||||
INFO swap::cli::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit_until_swap_will_start=0.00991000 BTC max_deposit_until_maximum_amount_is_reached=20999999.99991000 BTC max_giveable=0.00010000 BTC minimum_amount=0.01000000 BTC maximum_amount=21000000 BTC min_bitcoin_lock_tx_fee=0.00001000 BTC price=0.00100000 BTC
|
||||
INFO swap::cli::api::request: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC
|
||||
"
|
||||
);
|
||||
}
|
||||
|
|
@ -277,11 +271,11 @@ mod tests {
|
|||
async fn given_no_initial_balance_and_transfers_less_than_min_keep_waiting() {
|
||||
let writer = capture_logs(LevelFilter::INFO);
|
||||
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
|
||||
Amount::ZERO,
|
||||
Amount::from_btc(0.01).unwrap(),
|
||||
Amount::from_btc(0.01).unwrap(),
|
||||
Amount::from_btc(0.01).unwrap(),
|
||||
Amount::from_btc(0.01).unwrap(),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::from_btc(0.01).unwrap(), Amount::from_sat(1000)),
|
||||
(Amount::from_btc(0.01).unwrap(), Amount::from_sat(1000)),
|
||||
(Amount::from_btc(0.01).unwrap(), Amount::from_sat(1000)),
|
||||
(Amount::from_btc(0.01).unwrap(), Amount::from_sat(1000)),
|
||||
])));
|
||||
|
||||
let error = tokio::time::timeout(
|
||||
|
|
@ -296,7 +290,6 @@ mod tests {
|
|||
result.give()
|
||||
},
|
||||
|| async { Ok(()) },
|
||||
|_| async { Ok(Amount::from_sat(1000)) },
|
||||
None,
|
||||
None,
|
||||
),
|
||||
|
|
@ -307,13 +300,13 @@ mod tests {
|
|||
assert!(matches!(error, tokio::time::error::Elapsed { .. }));
|
||||
assert_eq!(
|
||||
writer.captured(),
|
||||
r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC
|
||||
INFO swap::api::request: Deposit at least 0.10001 BTC to cover the min quantity with fee!
|
||||
INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001 BTC max_giveable=0 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC
|
||||
INFO swap::api::request: Received Bitcoin new_balance=0.0101 BTC max_giveable=0.01 BTC
|
||||
INFO swap::api::request: Deposited amount is less than `min_quantity`
|
||||
INFO swap::api::request: Deposit at least 0.09001 BTC to cover the min quantity with fee!
|
||||
INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.09001 BTC max_giveable=0.01 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC
|
||||
r" INFO swap::cli::api::request: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=21000000 BTC
|
||||
INFO swap::cli::api::request: Deposit at least 0.10001000 BTC to cover the min quantity with fee!
|
||||
INFO swap::cli::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit_until_swap_will_start=0.10001000 BTC max_deposit_until_maximum_amount_is_reached=21000000.00001000 BTC max_giveable=0 BTC minimum_amount=0.10000000 BTC maximum_amount=21000000 BTC min_bitcoin_lock_tx_fee=0.00001000 BTC price=0.00100000 BTC
|
||||
INFO swap::cli::api::request: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC
|
||||
INFO swap::cli::api::request: Deposited amount is not enough to cover `min_quantity` when accounting for network fees
|
||||
INFO swap::cli::api::request: Deposit at least 0.09001000 BTC to cover the min quantity with fee!
|
||||
INFO swap::cli::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit_until_swap_will_start=0.09001000 BTC max_deposit_until_maximum_amount_is_reached=20999999.99001000 BTC max_giveable=0.01000000 BTC minimum_amount=0.10000000 BTC maximum_amount=21000000 BTC min_bitcoin_lock_tx_fee=0.00001000 BTC price=0.00100000 BTC
|
||||
"
|
||||
);
|
||||
}
|
||||
|
|
@ -322,16 +315,16 @@ mod tests {
|
|||
async fn given_longer_delay_until_deposit_should_not_spam_user() {
|
||||
let writer = capture_logs(LevelFilter::INFO);
|
||||
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
|
||||
Amount::ZERO,
|
||||
Amount::ZERO,
|
||||
Amount::ZERO,
|
||||
Amount::ZERO,
|
||||
Amount::ZERO,
|
||||
Amount::ZERO,
|
||||
Amount::ZERO,
|
||||
Amount::ZERO,
|
||||
Amount::ZERO,
|
||||
Amount::from_btc(0.2).unwrap(),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::ZERO, Amount::from_sat(1000)),
|
||||
(Amount::from_btc(0.2).unwrap(), Amount::from_sat(1000)),
|
||||
])));
|
||||
|
||||
tokio::time::timeout(
|
||||
|
|
@ -347,7 +340,6 @@ mod tests {
|
|||
result.give()
|
||||
},
|
||||
|| async { Ok(()) },
|
||||
|_| async { Ok(Amount::from_sat(1000)) },
|
||||
None,
|
||||
None,
|
||||
),
|
||||
|
|
@ -358,10 +350,10 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
writer.captured(),
|
||||
r" INFO swap::api::request: Received quote price=0.001 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC
|
||||
INFO swap::api::request: Deposit at least 0.10001 BTC to cover the min quantity with fee!
|
||||
INFO swap::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit=0.10001 BTC max_giveable=0 BTC minimum_amount=0.1 BTC maximum_amount=184467440737.09551615 BTC
|
||||
INFO swap::api::request: Received Bitcoin new_balance=0.21 BTC max_giveable=0.2 BTC
|
||||
r" INFO swap::cli::api::request: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=21000000 BTC
|
||||
INFO swap::cli::api::request: Deposit at least 0.10001000 BTC to cover the min quantity with fee!
|
||||
INFO swap::cli::api::request: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 min_deposit_until_swap_will_start=0.10001000 BTC max_deposit_until_maximum_amount_is_reached=21000000.00001000 BTC max_giveable=0 BTC minimum_amount=0.10000000 BTC maximum_amount=21000000 BTC min_bitcoin_lock_tx_fee=0.00001000 BTC price=0.00100000 BTC
|
||||
INFO swap::cli::api::request: Received Bitcoin new_balance=0.21000000 BTC max_giveable=0.20000000 BTC
|
||||
"
|
||||
);
|
||||
}
|
||||
|
|
@ -369,8 +361,8 @@ mod tests {
|
|||
#[tokio::test]
|
||||
async fn given_bid_quote_max_amount_0_return_error() {
|
||||
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
|
||||
Amount::from_btc(0.0001).unwrap(),
|
||||
Amount::from_btc(0.01).unwrap(),
|
||||
(Amount::from_btc(0.0001).unwrap(), Amount::from_sat(1000)),
|
||||
(Amount::from_btc(0.01).unwrap(), Amount::from_sat(1000)),
|
||||
])));
|
||||
|
||||
let determination_error = determine_btc_to_swap(
|
||||
|
|
@ -383,7 +375,6 @@ mod tests {
|
|||
result.give()
|
||||
},
|
||||
|| async { Ok(()) },
|
||||
|_| async { Ok(Amount::from_sat(1000)) },
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
|
@ -396,18 +387,18 @@ mod tests {
|
|||
}
|
||||
|
||||
struct MaxGiveable {
|
||||
amounts: Vec<Amount>,
|
||||
amounts: Vec<(Amount, Amount)>,
|
||||
call_counter: usize,
|
||||
}
|
||||
|
||||
impl MaxGiveable {
|
||||
fn new(amounts: Vec<Amount>) -> Self {
|
||||
fn new(amounts: Vec<(Amount, Amount)>) -> Self {
|
||||
Self {
|
||||
amounts,
|
||||
call_counter: 0,
|
||||
}
|
||||
}
|
||||
fn give(&mut self) -> Result<Amount> {
|
||||
fn give(&mut self) -> Result<(Amount, Amount)> {
|
||||
let amount = self
|
||||
.amounts
|
||||
.get(self.call_counter)
|
||||
|
|
@ -428,7 +419,7 @@ mod tests {
|
|||
fn quote_with_min(btc: f64) -> BidQuote {
|
||||
BidQuote {
|
||||
price: Amount::from_btc(0.001).unwrap(),
|
||||
max_quantity: Amount::MAX,
|
||||
max_quantity: Amount::MAX_MONEY,
|
||||
min_quantity: Amount::from_btc(btc).unwrap(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -574,6 +574,11 @@ mod tests {
|
|||
.estimate_fee(TxPunish::weight(), btc_amount)
|
||||
.await
|
||||
.unwrap();
|
||||
let tx_lock_fee = alice_wallet
|
||||
.estimate_fee(TxLock::weight(), btc_amount)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let redeem_address = alice_wallet.new_address().await.unwrap();
|
||||
let punish_address = alice_wallet.new_address().await.unwrap();
|
||||
|
||||
|
|
@ -600,6 +605,7 @@ mod tests {
|
|||
config.monero_finality_confirmations,
|
||||
spending_fee,
|
||||
spending_fee,
|
||||
tx_lock_fee,
|
||||
);
|
||||
|
||||
let message0 = bob_state0.next_message();
|
||||
|
|
@ -633,10 +639,10 @@ mod tests {
|
|||
.unwrap();
|
||||
let refund_transaction = bob_state6.signed_refund_transaction().unwrap();
|
||||
|
||||
assert_weight(redeem_transaction, TxRedeem::weight() as u64, "TxRedeem");
|
||||
assert_weight(cancel_transaction, TxCancel::weight() as u64, "TxCancel");
|
||||
assert_weight(punish_transaction, TxPunish::weight() as u64, "TxPunish");
|
||||
assert_weight(refund_transaction, TxRefund::weight() as u64, "TxRefund");
|
||||
assert_weight(redeem_transaction, TxRedeem::weight().to_wu(), "TxRedeem");
|
||||
assert_weight(cancel_transaction, TxCancel::weight().to_wu(), "TxCancel");
|
||||
assert_weight(punish_transaction, TxPunish::weight().to_wu(), "TxPunish");
|
||||
assert_weight(refund_transaction, TxRefund::weight().to_wu(), "TxRefund");
|
||||
}
|
||||
|
||||
// Weights fluctuate because of the length of the signatures. Valid ecdsa
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::bitcoin::{
|
|||
};
|
||||
use ::bitcoin::sighash::SighashCache;
|
||||
use ::bitcoin::transaction::Version;
|
||||
use ::bitcoin::Weight;
|
||||
use ::bitcoin::{
|
||||
locktime::absolute::LockTime as PackedLockTime, secp256k1, sighash::SegwitV0Sighash as Sighash,
|
||||
EcdsaSighashType, OutPoint, ScriptBuf, Sequence, TxIn, TxOut, Txid,
|
||||
|
|
@ -283,8 +284,8 @@ impl TxCancel {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn weight() -> usize {
|
||||
596
|
||||
pub fn weight() -> Weight {
|
||||
Weight::from_wu(596)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ impl TxLock {
|
|||
impl EstimateFeeRate + Send + Sync + 'static,
|
||||
>,
|
||||
amount: Amount,
|
||||
spending_fee: Amount,
|
||||
A: PublicKey,
|
||||
B: PublicKey,
|
||||
change: bitcoin::Address,
|
||||
|
|
@ -38,7 +39,7 @@ impl TxLock {
|
|||
.expect("can derive address from descriptor");
|
||||
|
||||
let psbt = wallet
|
||||
.send_to_address(address, amount, Some(change))
|
||||
.send_to_address(address, amount, spending_fee, Some(change))
|
||||
.await?;
|
||||
|
||||
Ok(Self {
|
||||
|
|
@ -177,8 +178,8 @@ impl TxLock {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn weight() -> usize {
|
||||
TX_LOCK_WEIGHT
|
||||
pub fn weight() -> ::bitcoin::Weight {
|
||||
::bitcoin::Weight::from_wu(TX_LOCK_WEIGHT as u64)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -201,15 +202,28 @@ impl Watchable for TxLock {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bitcoin::TestWalletBuilder;
|
||||
use crate::bitcoin::wallet::TestWalletBuilder;
|
||||
use crate::bitcoin::Amount;
|
||||
use ::bitcoin::psbt::Psbt as PartiallySignedTransaction;
|
||||
|
||||
// Basic setup function for tests
|
||||
async fn setup() -> (
|
||||
PublicKey,
|
||||
PublicKey,
|
||||
Wallet<bdk_wallet::rusqlite::Connection, crate::bitcoin::wallet::StaticFeeRate>,
|
||||
) {
|
||||
let (A, B) = alice_and_bob();
|
||||
let wallet = TestWalletBuilder::new(100_000).build().await;
|
||||
(A, B, wallet)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_bob_sends_good_psbt_when_reconstructing_then_succeeeds() {
|
||||
let (A, B) = alice_and_bob();
|
||||
let wallet = TestWalletBuilder::new(50_000).build().await;
|
||||
let (A, B, wallet) = setup().await;
|
||||
let agreed_amount = Amount::from_sat(10000);
|
||||
let spending_fee = Amount::from_sat(1000);
|
||||
|
||||
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
|
||||
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount, spending_fee).await;
|
||||
let result = TxLock::from_psbt(psbt, A, B, agreed_amount);
|
||||
|
||||
result.expect("PSBT to be valid");
|
||||
|
|
@ -217,31 +231,28 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn bob_can_fund_without_a_change_output() {
|
||||
let (A, B) = alice_and_bob();
|
||||
let fees = 300;
|
||||
let agreed_amount = Amount::from_sat(10000);
|
||||
let amount = agreed_amount.to_sat() + fees;
|
||||
let wallet = TestWalletBuilder::new(amount).build().await;
|
||||
let (A, B, _) = setup().await;
|
||||
let amount = 10_000;
|
||||
let agreed_amount = Amount::from_sat(amount);
|
||||
let spending_fee = Amount::from_sat(300);
|
||||
let wallet = TestWalletBuilder::new(amount + 300).build().await;
|
||||
|
||||
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
|
||||
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount, spending_fee).await;
|
||||
assert_eq!(
|
||||
psbt.unsigned_tx.output.len(),
|
||||
1,
|
||||
"psbt should only have a single output"
|
||||
"Expected no change output"
|
||||
);
|
||||
let result = TxLock::from_psbt(psbt, A, B, agreed_amount);
|
||||
|
||||
result.expect("PSBT to be valid");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn given_bob_is_sending_less_than_agreed_when_reconstructing_txlock_then_fails() {
|
||||
let (A, B) = alice_and_bob();
|
||||
let wallet = TestWalletBuilder::new(50_000).build().await;
|
||||
let (A, B, wallet) = setup().await;
|
||||
let agreed_amount = Amount::from_sat(10000);
|
||||
let spending_fee = Amount::from_sat(1000);
|
||||
|
||||
let bad_amount = Amount::from_sat(5000);
|
||||
let psbt = bob_make_psbt(A, B, &wallet, bad_amount).await;
|
||||
let psbt = bob_make_psbt(A, B, &wallet, bad_amount, spending_fee).await;
|
||||
let result = TxLock::from_psbt(psbt, A, B, agreed_amount);
|
||||
|
||||
result.expect_err("PSBT to be invalid");
|
||||
|
|
@ -249,12 +260,12 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn given_bob_is_sending_to_a_bad_output_reconstructing_txlock_then_fails() {
|
||||
let (A, B) = alice_and_bob();
|
||||
let wallet = TestWalletBuilder::new(50_000).build().await;
|
||||
let (A, B, wallet) = setup().await;
|
||||
let agreed_amount = Amount::from_sat(10000);
|
||||
let spending_fee = Amount::from_sat(1000);
|
||||
|
||||
let E = eve();
|
||||
let psbt = bob_make_psbt(E, B, &wallet, agreed_amount).await;
|
||||
let psbt = bob_make_psbt(E, B, &wallet, agreed_amount, spending_fee).await;
|
||||
let result = TxLock::from_psbt(psbt, A, B, agreed_amount);
|
||||
|
||||
result.expect_err("PSBT to be invalid");
|
||||
|
|
@ -271,9 +282,7 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// Helper function that represents Bob's action of constructing the PSBT.
|
||||
///
|
||||
/// Extracting this allows us to keep the tests concise.
|
||||
// Helper function for testing PSBT creation by Bob
|
||||
async fn bob_make_psbt(
|
||||
A: PublicKey,
|
||||
B: PublicKey,
|
||||
|
|
@ -282,9 +291,10 @@ mod tests {
|
|||
impl EstimateFeeRate + Send + Sync + 'static,
|
||||
>,
|
||||
amount: Amount,
|
||||
spending_fee: Amount,
|
||||
) -> PartiallySignedTransaction {
|
||||
let change = wallet.new_address().await.unwrap();
|
||||
TxLock::new(wallet, amount, A, B, change)
|
||||
TxLock::new(wallet, amount, spending_fee, A, B, change)
|
||||
.await
|
||||
.unwrap()
|
||||
.into()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use crate::bitcoin::wallet::Watchable;
|
||||
use crate::bitcoin::{self, Address, Amount, PunishTimelock, Transaction, TxCancel, Txid};
|
||||
use ::bitcoin::sighash::SighashCache;
|
||||
use ::bitcoin::ScriptBuf;
|
||||
use ::bitcoin::{secp256k1, sighash::SegwitV0Sighash as Sighash, EcdsaSighashType};
|
||||
use ::bitcoin::{ScriptBuf, Weight};
|
||||
use anyhow::{Context, Result};
|
||||
use bdk_wallet::miniscript::Descriptor;
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -93,8 +93,8 @@ impl TxPunish {
|
|||
Ok(tx_punish)
|
||||
}
|
||||
|
||||
pub fn weight() -> usize {
|
||||
548
|
||||
pub fn weight() -> Weight {
|
||||
Weight::from_wu(548)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ use ::bitcoin::{sighash::SegwitV0Sighash as Sighash, Txid};
|
|||
use anyhow::{bail, Context, Result};
|
||||
use bdk_wallet::miniscript::Descriptor;
|
||||
use bitcoin::sighash::SighashCache;
|
||||
use bitcoin::EcdsaSighashType;
|
||||
use bitcoin::{secp256k1, ScriptBuf};
|
||||
use bitcoin::{EcdsaSighashType, Weight};
|
||||
use ecdsa_fun::adaptor::{Adaptor, HashTranscript};
|
||||
use ecdsa_fun::fun::Scalar;
|
||||
use ecdsa_fun::nonce::Deterministic;
|
||||
|
|
@ -145,8 +145,8 @@ impl TxRedeem {
|
|||
Ok(sig)
|
||||
}
|
||||
|
||||
pub fn weight() -> usize {
|
||||
548
|
||||
pub fn weight() -> Weight {
|
||||
Weight::from_wu(548)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::bitcoin::{
|
|||
};
|
||||
use crate::{bitcoin, monero};
|
||||
use ::bitcoin::sighash::SighashCache;
|
||||
use ::bitcoin::{secp256k1, ScriptBuf};
|
||||
use ::bitcoin::{secp256k1, ScriptBuf, Weight};
|
||||
use ::bitcoin::{sighash::SegwitV0Sighash as Sighash, EcdsaSighashType, Txid};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use bdk_wallet::miniscript::Descriptor;
|
||||
|
|
@ -152,8 +152,8 @@ impl TxRefund {
|
|||
Ok(sig)
|
||||
}
|
||||
|
||||
pub fn weight() -> usize {
|
||||
548
|
||||
pub fn weight() -> Weight {
|
||||
Weight::from_wu(548)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -729,8 +729,10 @@ pub async fn buy_xmr(
|
|||
}
|
||||
},
|
||||
swap_result = async {
|
||||
let max_givable = || bitcoin_wallet.max_giveable(TxLock::script_size());
|
||||
let estimate_fee = |amount| bitcoin_wallet.estimate_fee(TxLock::weight(), amount);
|
||||
let max_givable = || async {
|
||||
let (amount, fee) = bitcoin_wallet.max_giveable(TxLock::script_size()).await?;
|
||||
Ok((amount, fee))
|
||||
};
|
||||
|
||||
let determine_amount = determine_btc_to_swap(
|
||||
context.config.json,
|
||||
|
|
@ -739,12 +741,11 @@ pub async fn buy_xmr(
|
|||
|| bitcoin_wallet.balance(),
|
||||
max_givable,
|
||||
|| bitcoin_wallet.sync(),
|
||||
estimate_fee,
|
||||
context.tauri_handle.clone(),
|
||||
Some(swap_id)
|
||||
);
|
||||
|
||||
let (amount, fees) = match determine_amount.await {
|
||||
let (tx_lock_amount, tx_lock_fee) = match determine_amount.await {
|
||||
Ok(val) => val,
|
||||
Err(error) => match error.downcast::<ZeroQuoteReceived>() {
|
||||
Ok(_) => {
|
||||
|
|
@ -754,7 +755,7 @@ pub async fn buy_xmr(
|
|||
},
|
||||
};
|
||||
|
||||
tracing::info!(%amount, %fees, "Determined swap amount");
|
||||
tracing::info!(%tx_lock_amount, %tx_lock_fee, "Determined swap amount");
|
||||
|
||||
context.db.insert_peer_id(swap_id, seller_peer_id).await?;
|
||||
|
||||
|
|
@ -767,7 +768,8 @@ pub async fn buy_xmr(
|
|||
event_loop_handle,
|
||||
monero_receive_address,
|
||||
bitcoin_change_address,
|
||||
amount,
|
||||
tx_lock_amount,
|
||||
tx_lock_fee
|
||||
).with_event_emitter(context.tauri_handle.clone());
|
||||
|
||||
bob::run(swap).await
|
||||
|
|
@ -1004,25 +1006,39 @@ pub async fn withdraw_btc(
|
|||
.as_ref()
|
||||
.context("Could not get Bitcoin wallet")?;
|
||||
|
||||
let amount = match amount {
|
||||
Some(amount) => amount,
|
||||
let (withdraw_tx_unsigned, amount) = match amount {
|
||||
Some(amount) => {
|
||||
let withdraw_tx_unsigned = bitcoin_wallet
|
||||
.send_to_address_dynamic_fee(address, amount, None)
|
||||
.await?;
|
||||
|
||||
(withdraw_tx_unsigned, amount)
|
||||
}
|
||||
None => {
|
||||
bitcoin_wallet
|
||||
let (max_giveable, spending_fee) = bitcoin_wallet
|
||||
.max_giveable(address.script_pubkey().len())
|
||||
.await?
|
||||
.await?;
|
||||
|
||||
let withdraw_tx_unsigned = bitcoin_wallet
|
||||
.send_to_address(address, max_giveable, spending_fee, None)
|
||||
.await?;
|
||||
|
||||
(withdraw_tx_unsigned, max_giveable)
|
||||
}
|
||||
};
|
||||
let psbt = bitcoin_wallet
|
||||
.send_to_address(address, amount, None)
|
||||
|
||||
let withdraw_tx = bitcoin_wallet
|
||||
.sign_and_finalize(withdraw_tx_unsigned)
|
||||
.await?;
|
||||
let signed_tx = bitcoin_wallet.sign_and_finalize(psbt).await?;
|
||||
|
||||
bitcoin_wallet
|
||||
.broadcast(signed_tx.clone(), "withdraw")
|
||||
.broadcast(withdraw_tx.clone(), "withdraw")
|
||||
.await?;
|
||||
|
||||
let txid = withdraw_tx.compute_txid();
|
||||
|
||||
Ok(WithdrawBtcResponse {
|
||||
txid: signed_tx.compute_txid().to_string(),
|
||||
txid: txid.to_string(),
|
||||
amount,
|
||||
})
|
||||
}
|
||||
|
|
@ -1175,26 +1191,23 @@ fn qr_code(value: &impl ToString) -> Result<String> {
|
|||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS, FFE, TFE>(
|
||||
pub async fn determine_btc_to_swap<FB, TB, FMG, TMG, FS, TS>(
|
||||
json: bool,
|
||||
bid_quote: BidQuote,
|
||||
get_new_address: impl Future<Output = Result<bitcoin::Address>>,
|
||||
balance: FB,
|
||||
max_giveable_fn: FMG,
|
||||
sync: FS,
|
||||
estimate_fee: FFE,
|
||||
event_emitter: Option<TauriHandle>,
|
||||
swap_id: Option<Uuid>,
|
||||
) -> Result<(bitcoin::Amount, bitcoin::Amount)>
|
||||
where
|
||||
TB: Future<Output = Result<bitcoin::Amount>>,
|
||||
FB: Fn() -> TB,
|
||||
TMG: Future<Output = Result<bitcoin::Amount>>,
|
||||
TMG: Future<Output = Result<(bitcoin::Amount, bitcoin::Amount)>>,
|
||||
FMG: Fn() -> TMG,
|
||||
TS: Future<Output = Result<()>>,
|
||||
FS: Fn() -> TS,
|
||||
FFE: Fn(bitcoin::Amount) -> TFE,
|
||||
TFE: Future<Output = Result<bitcoin::Amount>>,
|
||||
{
|
||||
if bid_quote.max_quantity == bitcoin::Amount::ZERO {
|
||||
bail!(ZeroQuoteReceived)
|
||||
|
|
@ -1208,23 +1221,30 @@ where
|
|||
);
|
||||
|
||||
sync().await.context("Failed to sync of Bitcoin wallet")?;
|
||||
let mut max_giveable = max_giveable_fn().await?;
|
||||
let (mut max_giveable, mut spending_fee) = max_giveable_fn().await?;
|
||||
|
||||
if max_giveable == bitcoin::Amount::ZERO || max_giveable < bid_quote.min_quantity {
|
||||
let deposit_address = get_new_address.await?;
|
||||
let minimum_amount = bid_quote.min_quantity;
|
||||
let maximum_amount = bid_quote.max_quantity;
|
||||
|
||||
// To avoid any issus, we clip maximum_amount to never go above the
|
||||
// total maximim Bitcoin supply
|
||||
let maximum_amount = maximum_amount.min(bitcoin::Amount::MAX_MONEY);
|
||||
|
||||
if !json {
|
||||
eprintln!("{}", qr_code(&deposit_address)?);
|
||||
}
|
||||
|
||||
loop {
|
||||
let min_outstanding = bid_quote.min_quantity - max_giveable;
|
||||
let min_bitcoin_lock_tx_fee = estimate_fee(min_outstanding).await?;
|
||||
let min_bitcoin_lock_tx_fee = spending_fee; // Use the fee from max_giveable
|
||||
let min_deposit_until_swap_will_start = min_outstanding + min_bitcoin_lock_tx_fee;
|
||||
let max_deposit_until_maximum_amount_is_reached =
|
||||
maximum_amount - max_giveable + min_bitcoin_lock_tx_fee;
|
||||
let max_deposit_until_maximum_amount_is_reached = maximum_amount
|
||||
.checked_sub(max_giveable)
|
||||
.context("Overflow when subtracting max_giveable from maximum_amount")?
|
||||
.checked_add(min_bitcoin_lock_tx_fee)
|
||||
.context(format!("Overflow when adding min_bitcoin_lock_tx_fee ({min_bitcoin_lock_tx_fee}) to max_giveable ({max_giveable}) with maximum_amount ({maximum_amount})"))?;
|
||||
|
||||
tracing::info!(
|
||||
"Deposit at least {} to cover the min quantity with fee!",
|
||||
|
|
@ -1256,14 +1276,14 @@ where
|
|||
);
|
||||
}
|
||||
|
||||
max_giveable = loop {
|
||||
(max_giveable, spending_fee) = loop {
|
||||
sync()
|
||||
.await
|
||||
.context("Failed to sync Bitcoin wallet while waiting for deposit")?;
|
||||
let new_max_givable = max_giveable_fn().await?;
|
||||
let (new_max_givable, new_fee) = max_giveable_fn().await?;
|
||||
|
||||
if new_max_givable > max_giveable {
|
||||
break new_max_givable;
|
||||
break (new_max_givable, new_fee);
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
use super::request::BalanceResponse;
|
||||
use crate::bitcoin;
|
||||
use crate::{bitcoin::ExpiredTimelocks, monero, network::quote::BidQuote};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
|
|
@ -14,8 +15,6 @@ use typeshare::typeshare;
|
|||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::request::BalanceResponse;
|
||||
|
||||
#[typeshare]
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(tag = "channelName", content = "event")]
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pub enum Bob {
|
|||
btc_amount: bitcoin::Amount,
|
||||
#[serde(with = "crate::bitcoin::address_serde")]
|
||||
change_address: bitcoin::Address,
|
||||
tx_lock_fee: bitcoin::Amount,
|
||||
},
|
||||
ExecutionSetupDone {
|
||||
state2: bob::State2,
|
||||
|
|
@ -54,9 +55,11 @@ impl From<BobState> for Bob {
|
|||
BobState::Started {
|
||||
btc_amount,
|
||||
change_address,
|
||||
tx_lock_fee,
|
||||
} => Bob::Started {
|
||||
btc_amount,
|
||||
change_address,
|
||||
tx_lock_fee,
|
||||
},
|
||||
BobState::SwapSetupCompleted(state2) => Bob::ExecutionSetupDone { state2 },
|
||||
BobState::BtcLocked {
|
||||
|
|
@ -96,9 +99,11 @@ impl From<Bob> for BobState {
|
|||
Bob::Started {
|
||||
btc_amount,
|
||||
change_address,
|
||||
tx_lock_fee,
|
||||
} => BobState::Started {
|
||||
btc_amount,
|
||||
change_address,
|
||||
tx_lock_fee,
|
||||
},
|
||||
Bob::ExecutionSetupDone { state2 } => BobState::SwapSetupCompleted(state2),
|
||||
Bob::BtcLocked {
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ impl Handler {
|
|||
pub struct NewSwap {
|
||||
pub swap_id: Uuid,
|
||||
pub btc: bitcoin::Amount,
|
||||
pub tx_lock_fee: bitcoin::Amount,
|
||||
pub tx_refund_fee: bitcoin::Amount,
|
||||
pub tx_cancel_fee: bitcoin::Amount,
|
||||
pub bitcoin_refund_address: bitcoin::Address,
|
||||
|
|
@ -211,10 +212,11 @@ impl ConnectionHandler for Handler {
|
|||
xmr,
|
||||
env_config.bitcoin_cancel_timelock,
|
||||
env_config.bitcoin_punish_timelock,
|
||||
new_swap_request.bitcoin_refund_address,
|
||||
new_swap_request.bitcoin_refund_address.clone(),
|
||||
env_config.monero_finality_confirmations,
|
||||
new_swap_request.tx_refund_fee,
|
||||
new_swap_request.tx_cancel_fee,
|
||||
new_swap_request.tx_lock_fee,
|
||||
);
|
||||
|
||||
write_cbor_message(&mut substream, state0.next_message())
|
||||
|
|
|
|||
|
|
@ -402,11 +402,11 @@ pub struct State3 {
|
|||
#[serde(with = "::bitcoin::amount::serde::as_sat")]
|
||||
tx_redeem_fee: bitcoin::Amount,
|
||||
#[serde(with = "::bitcoin::amount::serde::as_sat")]
|
||||
tx_punish_fee: bitcoin::Amount,
|
||||
pub tx_punish_fee: bitcoin::Amount,
|
||||
#[serde(with = "::bitcoin::amount::serde::as_sat")]
|
||||
tx_refund_fee: bitcoin::Amount,
|
||||
pub tx_refund_fee: bitcoin::Amount,
|
||||
#[serde(with = "::bitcoin::amount::serde::as_sat")]
|
||||
tx_cancel_fee: bitcoin::Amount,
|
||||
pub tx_cancel_fee: bitcoin::Amount,
|
||||
}
|
||||
|
||||
impl State3 {
|
||||
|
|
|
|||
|
|
@ -39,10 +39,12 @@ impl Swap {
|
|||
monero_receive_address: monero::Address,
|
||||
bitcoin_change_address: bitcoin::Address,
|
||||
btc_amount: bitcoin::Amount,
|
||||
tx_lock_fee: bitcoin::Amount,
|
||||
) -> Self {
|
||||
Self {
|
||||
state: BobState::Started {
|
||||
btc_amount,
|
||||
tx_lock_fee,
|
||||
change_address: bitcoin_change_address,
|
||||
},
|
||||
event_loop_handle,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ pub enum BobState {
|
|||
Started {
|
||||
#[serde(with = "::bitcoin::amount::serde::as_sat")]
|
||||
btc_amount: bitcoin::Amount,
|
||||
tx_lock_fee: bitcoin::Amount,
|
||||
#[serde(with = "address_serde")]
|
||||
change_address: bitcoin::Address,
|
||||
},
|
||||
|
|
@ -124,6 +125,7 @@ pub struct State0 {
|
|||
min_monero_confirmations: u64,
|
||||
tx_refund_fee: bitcoin::Amount,
|
||||
tx_cancel_fee: bitcoin::Amount,
|
||||
tx_lock_fee: bitcoin::Amount,
|
||||
}
|
||||
|
||||
impl State0 {
|
||||
|
|
@ -139,6 +141,7 @@ impl State0 {
|
|||
min_monero_confirmations: u64,
|
||||
tx_refund_fee: bitcoin::Amount,
|
||||
tx_cancel_fee: bitcoin::Amount,
|
||||
tx_lock_fee: bitcoin::Amount,
|
||||
) -> Self {
|
||||
let b = bitcoin::SecretKey::new_random(rng);
|
||||
|
||||
|
|
@ -165,6 +168,7 @@ impl State0 {
|
|||
min_monero_confirmations,
|
||||
tx_refund_fee,
|
||||
tx_cancel_fee,
|
||||
tx_lock_fee,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -208,6 +212,7 @@ impl State0 {
|
|||
let tx_lock = bitcoin::TxLock::new(
|
||||
wallet,
|
||||
self.btc,
|
||||
self.tx_lock_fee,
|
||||
msg.A,
|
||||
self.b.public(),
|
||||
self.refund_address.clone(),
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ async fn next_state(
|
|||
BobState::Started {
|
||||
btc_amount,
|
||||
change_address,
|
||||
tx_lock_fee,
|
||||
} => {
|
||||
let tx_refund_fee = bitcoin_wallet
|
||||
.estimate_fee(TxRefund::weight(), btc_amount)
|
||||
|
|
@ -129,6 +130,7 @@ async fn next_state(
|
|||
.setup_swap(NewSwap {
|
||||
swap_id,
|
||||
btc: btc_amount,
|
||||
tx_lock_fee,
|
||||
tx_refund_fee,
|
||||
tx_cancel_fee,
|
||||
bitcoin_refund_address: change_address,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use std::path::PathBuf;
|
|||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use swap::asb::FixedRate;
|
||||
use swap::bitcoin::{CancelTimelock, PunishTimelock, TxCancel, TxPunish, TxRedeem, TxRefund};
|
||||
use swap::bitcoin::{CancelTimelock, PunishTimelock};
|
||||
use swap::cli::api;
|
||||
use swap::database::{AccessMode, SqliteDatabase};
|
||||
use swap::env::{Config, GetConfig};
|
||||
|
|
@ -486,6 +486,7 @@ impl BobParams {
|
|||
self.monero_wallet.lock().await.get_main_address(),
|
||||
self.bitcoin_wallet.new_address().await?,
|
||||
btc_amount,
|
||||
bitcoin::Amount::from_sat(1000), // Fixed fee of 1000 satoshis for now
|
||||
);
|
||||
|
||||
Ok((swap, event_loop))
|
||||
|
|
@ -655,12 +656,16 @@ impl TestContext {
|
|||
}
|
||||
|
||||
pub async fn assert_alice_punished(&self, state: AliceState) {
|
||||
assert!(matches!(state, AliceState::BtcPunished { .. }));
|
||||
let (cancel_fee, punish_fee) = match state {
|
||||
AliceState::BtcPunished { state3 } => (state3.tx_cancel_fee, state3.tx_punish_fee),
|
||||
_ => panic!("Alice is not in btc punished state: {:?}", state),
|
||||
};
|
||||
|
||||
assert_eventual_balance(
|
||||
self.alice_bitcoin_wallet.as_ref(),
|
||||
Ordering::Equal,
|
||||
self.alice_punished_btc_balance().await,
|
||||
self.alice_punished_btc_balance(cancel_fee, punish_fee)
|
||||
.await,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
|
@ -698,10 +703,13 @@ impl TestContext {
|
|||
pub async fn assert_bob_refunded(&self, state: BobState) {
|
||||
self.bob_bitcoin_wallet.sync().await.unwrap();
|
||||
|
||||
let lock_tx_id = if let BobState::BtcRefunded(state4) = state {
|
||||
state4.tx_lock_id()
|
||||
} else {
|
||||
panic!("Bob is not in btc refunded state: {:?}", state);
|
||||
let (lock_tx_id, cancel_fee, refund_fee) = match state {
|
||||
BobState::BtcRefunded(state6) => (
|
||||
state6.tx_lock_id(),
|
||||
state6.tx_cancel_fee,
|
||||
state6.tx_refund_fee,
|
||||
),
|
||||
_ => panic!("Bob is not in btc refunded state: {:?}", state),
|
||||
};
|
||||
let lock_tx_bitcoin_fee = self
|
||||
.bob_bitcoin_wallet
|
||||
|
|
@ -711,17 +719,6 @@ impl TestContext {
|
|||
|
||||
let btc_balance_after_swap = self.bob_bitcoin_wallet.balance().await.unwrap();
|
||||
|
||||
let cancel_fee = self
|
||||
.alice_bitcoin_wallet
|
||||
.estimate_fee(TxCancel::weight(), self.btc_amount)
|
||||
.await
|
||||
.expect("To estimate fee correctly");
|
||||
let refund_fee = self
|
||||
.alice_bitcoin_wallet
|
||||
.estimate_fee(TxRefund::weight(), self.btc_amount)
|
||||
.await
|
||||
.expect("To estimate fee correctly");
|
||||
|
||||
let bob_cancelled_and_refunded = btc_balance_after_swap
|
||||
== self.bob_starting_balances.btc - lock_tx_bitcoin_fee - cancel_fee - refund_fee;
|
||||
|
||||
|
|
@ -759,11 +756,21 @@ impl TestContext {
|
|||
}
|
||||
|
||||
async fn alice_redeemed_btc_balance(&self) -> bitcoin::Amount {
|
||||
// Get the last transaction Alice published
|
||||
// This should be btc_redeem
|
||||
let txid = self
|
||||
.alice_bitcoin_wallet
|
||||
.last_published_txid()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Get the fee for the last transaction
|
||||
let fee = self
|
||||
.alice_bitcoin_wallet
|
||||
.estimate_fee(TxRedeem::weight(), self.btc_amount)
|
||||
.transaction_fee(txid)
|
||||
.await
|
||||
.expect("To estimate fee correctly");
|
||||
|
||||
self.alice_starting_balances.btc + self.btc_amount - fee
|
||||
}
|
||||
|
||||
|
|
@ -801,17 +808,11 @@ impl TestContext {
|
|||
self.alice_starting_balances.xmr - self.xmr_amount
|
||||
}
|
||||
|
||||
async fn alice_punished_btc_balance(&self) -> bitcoin::Amount {
|
||||
let cancel_fee = self
|
||||
.alice_bitcoin_wallet
|
||||
.estimate_fee(TxCancel::weight(), self.btc_amount)
|
||||
.await
|
||||
.expect("To estimate fee correctly");
|
||||
let punish_fee = self
|
||||
.alice_bitcoin_wallet
|
||||
.estimate_fee(TxPunish::weight(), self.btc_amount)
|
||||
.await
|
||||
.expect("To estimate fee correctly");
|
||||
async fn alice_punished_btc_balance(
|
||||
&self,
|
||||
cancel_fee: bitcoin::Amount,
|
||||
punish_fee: bitcoin::Amount,
|
||||
) -> bitcoin::Amount {
|
||||
self.alice_starting_balances.btc + self.btc_amount - cancel_fee - punish_fee
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue