From 0cdfa2057c213aacd0e4e1e6115de6af653b077a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Granh=C3=A3o?= <32176319+danielgranhao@users.noreply.github.com> Date: Tue, 21 Jan 2025 10:13:13 +0000 Subject: [PATCH] Prevent claiming of overpaid swaps (#667) * Handle overpayments gracefully * Fix test mock txs and swaps * Get actual payer amount from blockchain * Check first tx output instead of script balance * Fetch actual payer amount if not available * Remove user lockup amount verification in recoverer * Refactor user lockup amount check into appropriate method --- lib/core/src/chain_swap.rs | 125 ++++++++++++-- lib/core/src/persist/chain.rs | 9 +- lib/core/src/recover/recoverer.rs | 2 +- lib/core/src/sdk.rs | 78 +++++++-- lib/core/src/sync/mod.rs | 1 + lib/core/src/test_utils/chain.rs | 12 +- lib/core/src/test_utils/chain_swap.rs | 226 +++++++++++++------------- 7 files changed, 306 insertions(+), 147 deletions(-) diff --git a/lib/core/src/chain_swap.rs b/lib/core/src/chain_swap.rs index 39b073142..2b762c12e 100644 --- a/lib/core/src/chain_swap.rs +++ b/lib/core/src/chain_swap.rs @@ -1,6 +1,6 @@ use std::{str::FromStr, sync::Arc}; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; use boltz_client::{ boltz::{self}, @@ -211,6 +211,11 @@ impl ChainSwapHandler { .update_chain_swap_accept_zero_conf(&id, !zero_conf_rejected)?; } if let Some(transaction) = update.transaction.clone() { + let actual_payer_amount = + self.fetch_incoming_swap_actual_payer_amount(swap).await?; + self.persister + .update_actual_payer_amount(&swap.id, actual_payer_amount)?; + self.update_swap_info(&ChainSwapUpdate { swap_id: id, to_state: Pending, @@ -230,6 +235,11 @@ impl ChainSwapHandler { return Err(anyhow!("Unexpected payload from Boltz status stream")); }; + if let Err(e) = self.verify_user_lockup_tx(swap).await { + warn!("User lockup transaction for incoming Chain Swap {} could not be verified. err: {}", swap.id, e); + return Err(anyhow!("Could not verify user lockup transaction: {e}",)); + } + if let Err(e) = self .verify_server_lockup_tx(swap, &transaction, false) .await @@ -319,7 +329,7 @@ impl ChainSwapHandler { // If swap state is unrecoverable, either: // 1. The transaction failed - // 2. Lockup failed (too little funds were sent) + // 2. Lockup failed (too little/too much funds were sent) // 3. The claim lockup was refunded // 4. The swap has expired (>24h) // We initiate a cooperative refund, and then fallback to a regular one @@ -344,7 +354,7 @@ impl ChainSwapHandler { match swap.refund_tx_id.clone() { None => { warn!("Chain Swap {id} is in an unrecoverable state: {swap_state:?}"); - match self.verify_user_lockup_tx(swap).await { + match self.verify_user_lockup_tx_exists(swap).await { Ok(_) => { info!("Chain Swap {id} user lockup tx was broadcast. Setting the swap to refundable."); self.update_swap_info(&ChainSwapUpdate { @@ -576,6 +586,11 @@ impl ChainSwapHandler { return Err(anyhow!("Unexpected payload from Boltz status stream")); }; + if let Err(e) = self.verify_user_lockup_tx(swap).await { + warn!("User lockup transaction for outgoing Chain Swap {} could not be verified. err: {}", swap.id, e); + return Err(anyhow!("Could not verify user lockup transaction: {e}",)); + } + if let Err(e) = self .verify_server_lockup_tx(swap, &transaction, false) .await @@ -1165,6 +1180,47 @@ impl ChainSwapHandler { } } + async fn fetch_incoming_swap_actual_payer_amount(&self, chain_swap: &ChainSwap) -> Result { + let swap_script = chain_swap.get_lockup_swap_script()?; + let script_pubkey = swap_script + .as_bitcoin_script()? + .to_address(self.config.network.as_bitcoin_chain()) + .map_err(|e| anyhow!("Failed to get swap script address {e:?}"))? + .script_pubkey(); + + let history = self.fetch_bitcoin_script_history(&swap_script).await?; + + // User lockup tx is by definition the first + let first_tx_id = history + .first() + .ok_or(anyhow!( + "No history found for user lockup script for swap {}", + chain_swap.id + ))? + .txid + .to_raw_hash() + .into(); + + // Get full transaction + let txs = self + .bitcoin_chain_service + .lock() + .await + .get_transactions(&[first_tx_id])?; + let user_lockup_tx = txs.first().ok_or(anyhow!( + "No transactions found for user lockup script for swap {}", + chain_swap.id + ))?; + + // Find output paying to our script and get amount + user_lockup_tx + .output + .iter() + .find(|out| out.script_pubkey == script_pubkey) + .map(|out| out.value.to_sat()) + .ok_or(anyhow!("No output found paying to user lockup script")) + } + async fn verify_server_lockup_tx( &self, chain_swap: &ChainSwap, @@ -1218,7 +1274,7 @@ impl ChainSwapHandler { // Verify RBF let rbf_explicit = tx.input.iter().any(|tx_in| tx_in.sequence.is_rbf()); if !verify_confirmation && rbf_explicit { - return Err(anyhow!("Transaction signals RBF")); + bail!("Transaction signals RBF"); } // Verify amount let secp = Secp256k1::new(); @@ -1235,20 +1291,20 @@ impl ChainSwapHandler { match chain_swap.accepted_receiver_amount_sat { None => { if value < claim_details.amount { - return Err(anyhow!( + bail!( "Transaction value {value} sats is less than {} sats", claim_details.amount - )); + ); } } Some(accepted_receiver_amount_sat) => { let expected_server_lockup_amount_sat = accepted_receiver_amount_sat + chain_swap.claim_fees_sat; if value < expected_server_lockup_amount_sat { - return Err(anyhow!( + bail!( "Transaction value {value} sats is less than accepted {} sats", expected_server_lockup_amount_sat - )); + ); } } } @@ -1301,7 +1357,7 @@ impl ChainSwapHandler { Ok(()) } - async fn verify_user_lockup_tx(&self, chain_swap: &ChainSwap) -> Result { + async fn verify_user_lockup_tx_exists(&self, chain_swap: &ChainSwap) -> Result<()> { let swap_script = chain_swap.get_lockup_swap_script()?; let script_history = match chain_swap.direction { Direction::Incoming => self.fetch_bitcoin_script_history(&swap_script).await, @@ -1314,7 +1370,6 @@ impl ChainSwapHandler { .iter() .find(|h| h.txid.to_hex() == user_lockup_tx_id) .ok_or(anyhow!("Transaction was not found in script history"))?; - Ok(user_lockup_tx_id) } None => { let txid = script_history @@ -1328,9 +1383,37 @@ impl ChainSwapHandler { user_lockup_tx_id: Some(txid.clone()), ..Default::default() })?; - Ok(txid) } } + + Ok(()) + } + + async fn verify_user_lockup_tx(&self, chain_swap: &ChainSwap) -> Result<()> { + self.verify_user_lockup_tx_exists(chain_swap).await?; + + // Verify amount for incoming chain swaps + if chain_swap.direction == Direction::Incoming { + let actual_payer_amount_sat = match chain_swap.actual_payer_amount_sat { + Some(amount) => amount, + None => { + let actual_payer_amount_sat = self + .fetch_incoming_swap_actual_payer_amount(chain_swap) + .await?; + self.persister + .update_actual_payer_amount(&chain_swap.id, actual_payer_amount_sat)?; + actual_payer_amount_sat + } + }; + // For non-amountless swaps, make sure user locked up the agreed amount + if chain_swap.payer_amount_sat > 0 + && chain_swap.payer_amount_sat != actual_payer_amount_sat + { + bail!("Invalid user lockup tx - user lockup amount ({actual_payer_amount_sat} sat) differs from agreed ({} sat)", chain_swap.payer_amount_sat); + } + } + + Ok(()) } async fn fetch_bitcoin_script_history( @@ -1453,8 +1536,14 @@ mod tests { for (first_state, allowed_states) in valid_combinations.iter() { for allowed_state in allowed_states { - let chain_swap = - new_chain_swap(Direction::Incoming, Some(*first_state), false, None, false); + let chain_swap = new_chain_swap( + Direction::Incoming, + Some(*first_state), + false, + None, + false, + false, + ); persister.insert_or_update_chain_swap(&chain_swap)?; assert!(chain_swap_handler @@ -1480,8 +1569,14 @@ mod tests { for (first_state, disallowed_states) in invalid_combinations.iter() { for disallowed_state in disallowed_states { - let chain_swap = - new_chain_swap(Direction::Incoming, Some(*first_state), false, None, false); + let chain_swap = new_chain_swap( + Direction::Incoming, + Some(*first_state), + false, + None, + false, + false, + ); persister.insert_or_update_chain_swap(&chain_swap)?; assert!(chain_swap_handler diff --git a/lib/core/src/persist/chain.rs b/lib/core/src/persist/chain.rs index 83766b2c8..3906979ca 100644 --- a/lib/core/src/persist/chain.rs +++ b/lib/core/src/persist/chain.rs @@ -516,7 +516,7 @@ mod tests { async fn test_writing_stale_swap() -> Result<()> { create_persister!(storage); - let chain_swap = new_chain_swap(Direction::Incoming, None, false, None, false); + let chain_swap = new_chain_swap(Direction::Incoming, None, false, None, false, false); storage.insert_or_update_chain_swap(&chain_swap)?; // read - update - write works if there are no updates in between @@ -543,7 +543,7 @@ mod tests { create_persister!(storage); let chain_swap_local_with_sync_state = - new_chain_swap(Direction::Incoming, None, false, None, false); + new_chain_swap(Direction::Incoming, None, false, None, false, false); storage.insert_or_update_chain_swap(&chain_swap_local_with_sync_state)?; storage.set_sync_state(SyncState { data_id: chain_swap_local_with_sync_state.id.clone(), @@ -553,10 +553,11 @@ mod tests { })?; let chain_swap_local_no_sync_state = - new_chain_swap(Direction::Incoming, None, false, None, false); + new_chain_swap(Direction::Incoming, None, false, None, false, false); storage.insert_or_update_chain_swap(&chain_swap_local_no_sync_state)?; - let chain_swap_not_local = new_chain_swap(Direction::Incoming, None, false, None, false); + let chain_swap_not_local = + new_chain_swap(Direction::Incoming, None, false, None, false, false); storage.insert_or_update_chain_swap(&chain_swap_not_local)?; storage.set_sync_state(SyncState { data_id: chain_swap_not_local.id, diff --git a/lib/core/src/recover/recoverer.rs b/lib/core/src/recover/recoverer.rs index 5c9cb7cd7..899f8702a 100644 --- a/lib/core/src/recover/recoverer.rs +++ b/lib/core/src/recover/recoverer.rs @@ -662,7 +662,7 @@ impl Recoverer { Ok(res) } - /// Reconstruct Chain Receive Swap tx IDs from the onchain data and the immutable data data + /// Reconstruct Chain Receive Swap tx IDs from the onchain data and the immutable data fn recover_receive_chain_swap_tx_ids( &self, tx_map: &TxMap, diff --git a/lib/core/src/sdk.rs b/lib/core/src/sdk.rs index 16a3ecee2..a2550eccc 100644 --- a/lib/core/src/sdk.rs +++ b/lib/core/src/sdk.rs @@ -3117,13 +3117,17 @@ mod tests { use tokio::sync::Mutex; use crate::chain_swap::ESTIMATED_BTC_LOCKUP_TX_VSIZE; + use crate::test_utils::chain_swap::{ + TEST_BITCOIN_OUTGOING_SERVER_LOCKUP_TX, TEST_LIQUID_INCOMING_SERVER_LOCKUP_TX, + TEST_LIQUID_OUTGOING_USER_LOCKUP_TX, + }; use crate::test_utils::swapper::ZeroAmountSwapMockConfig; use crate::{ model::{Direction, PaymentState, Swap}, sdk::LiquidSdk, test_utils::{ chain::{MockBitcoinChainService, MockHistory, MockLiquidChainService}, - chain_swap::{new_chain_swap, TEST_BITCOIN_TX}, + chain_swap::{new_chain_swap, TEST_BITCOIN_INCOMING_USER_LOCKUP_TX}, persist::{create_persister, new_receive_swap, new_send_swap}, sdk::{new_liquid_sdk, new_liquid_sdk_with_chain_services}, status_stream::MockStatusStream, @@ -3139,6 +3143,7 @@ mod tests { initial_payment_state: Option, user_lockup_tx_id: Option, zero_amount: bool, + set_actual_payer_amount: bool, } impl Default for NewSwapArgs { @@ -3149,6 +3154,7 @@ mod tests { direction: Direction::Outgoing, user_lockup_tx_id: None, zero_amount: false, + set_actual_payer_amount: false, } } } @@ -3178,6 +3184,11 @@ mod tests { self.zero_amount = zero_amount; self } + + pub fn set_set_actual_payer_amount(mut self, set_actual_payer_amount: bool) -> Self { + self.set_actual_payer_amount = set_actual_payer_amount; + self + } } macro_rules! trigger_swap_update { @@ -3198,6 +3209,7 @@ mod tests { $args.accepts_zero_conf, $args.user_lockup_tx_id, $args.zero_amount, + $args.set_actual_payer_amount, ); $persister.insert_or_update_chain_swap(&swap).unwrap(); Swap::Chain(swap) @@ -3400,16 +3412,33 @@ mod tests { assert_eq!(persisted_swap.state, PaymentState::Failed); } - let (mock_tx_hex, mock_tx_id) = match direction { + let (mock_user_lockup_tx_hex, mock_user_lockup_tx_id) = match direction { + Direction::Outgoing => { + let tx = TEST_LIQUID_OUTGOING_USER_LOCKUP_TX.clone(); + ( + lwk_wollet::elements::encode::serialize(&tx).to_lower_hex_string(), + tx.txid().to_string(), + ) + } Direction::Incoming => { - let tx = TEST_LIQUID_TX.clone(); + let tx = TEST_BITCOIN_INCOMING_USER_LOCKUP_TX.clone(); + ( + sdk_common::bitcoin::consensus::serialize(&tx).to_lower_hex_string(), + tx.txid().to_string(), + ) + } + }; + + let (mock_server_lockup_tx_hex, mock_server_lockup_tx_id) = match direction { + Direction::Incoming => { + let tx = TEST_LIQUID_INCOMING_SERVER_LOCKUP_TX.clone(); ( lwk_wollet::elements::encode::serialize(&tx).to_lower_hex_string(), tx.txid().to_string(), ) } Direction::Outgoing => { - let tx = TEST_BITCOIN_TX.clone(); + let tx = TEST_BITCOIN_OUTGOING_SERVER_LOCKUP_TX.clone(); ( sdk_common::bitcoin::consensus::serialize(&tx).to_lower_hex_string(), tx.txid().to_string(), @@ -3420,7 +3449,7 @@ mod tests { // Verify that `TransactionLockupFailed` correctly sets the state as // `RefundPending`/`Refundable` or as `Failed` depending on whether or not // `user_lockup_tx_id` is present - for user_lockup_tx_id in &[None, Some(mock_tx_id.clone())] { + for user_lockup_tx_id in &[None, Some(mock_user_lockup_tx_id.clone())] { if let Some(user_lockup_tx_id) = user_lockup_tx_id { match direction { Direction::Incoming => { @@ -3476,6 +3505,21 @@ mod tests { ChainSwapStates::TransactionMempool, ChainSwapStates::TransactionConfirmed, ] { + if direction == Direction::Incoming { + bitcoin_chain_service + .lock() + .await + .set_history(vec![MockHistory { + txid: Txid::from_str(&mock_user_lockup_tx_id).unwrap(), + height: 0, + block_hash: None, + block_timestamp: None, + }]); + bitcoin_chain_service + .lock() + .await + .set_transactions(&[&mock_user_lockup_tx_hex]); + } let persisted_swap = trigger_swap_update!( "chain", NewSwapArgs::default().set_direction(direction), @@ -3483,12 +3527,15 @@ mod tests { status_stream, status, Some(SwapUpdateTxDetails { - id: mock_tx_id.clone(), - hex: mock_tx_hex.clone(), + id: mock_user_lockup_tx_id.clone(), + hex: mock_user_lockup_tx_hex.clone(), }), // sets `update.transaction` Some(true) // sets `update.zero_conf_rejected` ); - assert_eq!(persisted_swap.user_lockup_tx_id, Some(mock_tx_id.clone())); + assert_eq!( + persisted_swap.user_lockup_tx_id, + Some(mock_user_lockup_tx_id.clone()) + ); assert!(!persisted_swap.accept_zero_conf); } @@ -3502,13 +3549,14 @@ mod tests { "chain", NewSwapArgs::default() .set_direction(direction) - .set_accepts_zero_conf(accepts_zero_conf), + .set_accepts_zero_conf(accepts_zero_conf) + .set_set_actual_payer_amount(true), persister, status_stream, ChainSwapStates::TransactionServerMempool, Some(SwapUpdateTxDetails { - id: mock_tx_id.clone(), - hex: mock_tx_hex.clone(), + id: mock_server_lockup_tx_id.clone(), + hex: mock_server_lockup_tx_hex.clone(), }), None ); @@ -3528,13 +3576,15 @@ mod tests { // sets the payment as `Pending` and creates `claim_tx_id` let persisted_swap = trigger_swap_update!( "chain", - NewSwapArgs::default().set_direction(direction), + NewSwapArgs::default() + .set_direction(direction) + .set_set_actual_payer_amount(true), persister, status_stream, ChainSwapStates::TransactionServerConfirmed, Some(SwapUpdateTxDetails { - id: mock_tx_id, - hex: mock_tx_hex, + id: mock_server_lockup_tx_id, + hex: mock_server_lockup_tx_hex, }), None ); diff --git a/lib/core/src/sync/mod.rs b/lib/core/src/sync/mod.rs index 66ac3957c..ae43cbd45 100644 --- a/lib/core/src/sync/mod.rs +++ b/lib/core/src/sync/mod.rs @@ -595,6 +595,7 @@ mod tests { true, None, false, + false, ))?; sync_service.push().await?; diff --git a/lib/core/src/test_utils/chain.rs b/lib/core/src/test_utils/chain.rs index 1d2c682c7..bd75a8d86 100644 --- a/lib/core/src/test_utils/chain.rs +++ b/lib/core/src/test_utils/chain.rs @@ -127,6 +127,7 @@ impl LiquidChainService for MockLiquidChainService { pub(crate) struct MockBitcoinChainService { history: Vec, + txs: Vec, script_balance_sat: u64, } @@ -134,6 +135,7 @@ impl MockBitcoinChainService { pub(crate) fn new() -> Self { MockBitcoinChainService { history: vec![], + txs: vec![], script_balance_sat: 0, } } @@ -143,6 +145,14 @@ impl MockBitcoinChainService { self } + pub(crate) fn set_transactions(&mut self, txs: &[&str]) -> &mut Self { + self.txs = txs + .iter() + .map(|tx_hex| deserialize(&Vec::::from_hex(tx_hex).unwrap()).unwrap()) + .collect(); + self + } + pub(crate) fn set_script_balance_sat(&mut self, script_balance_sat: u64) -> &mut Self { self.script_balance_sat = script_balance_sat; self @@ -169,7 +179,7 @@ impl BitcoinChainService for MockBitcoinChainService { &self, _txids: &[boltz_client::bitcoin::Txid], ) -> Result> { - Ok(vec![]) + Ok(self.txs.clone()) } fn get_script_history(&self, _script: &Script) -> Result> { diff --git a/lib/core/src/test_utils/chain_swap.rs b/lib/core/src/test_utils/chain_swap.rs index 2d39d2ea2..d90b89646 100644 --- a/lib/core/src/test_utils/chain_swap.rs +++ b/lib/core/src/test_utils/chain_swap.rs @@ -23,7 +23,10 @@ use super::{ }; lazy_static! { - pub(crate) static ref TEST_BITCOIN_TX: Transaction = deserialize(&Vec::::from_hex("01000000000101da6af195321dfa98218c7deafa2da6d39d8d4a809a811de87269ddc4c4d28c810100000000ffffffff0c30c80700000000002251200894aacf46d0eed22594ed328b1e6806e94e662a4494f07cbca80720c3435e4130c807000000000022512098a3a5a9d34ebf22ced8f0056457164c9a9ee6c6eaef110c1a0cb465ac541d9130c807000000000022512050e1a1af89928af930b3bd0b826b40b2f3072c0009cd3186ae3ae23d0504f97930c80700000000002251201a55eb37d4331f8f367c0d4c727b565da089b8ab3d10e7079f1e3c2ae3b1123a30c8070000000000225120247e5ea29cb7bcec21b1bea1ed1f778adf887ab1bb04faaa014afd4ec8a2c0bb30c8070000000000225120330280e4540a00dace540ec2119d608024c11024a2bc8c7c8b652998fa42248330c80700000000002251202645e1ea344306e9068f1d086a08d22a30b0e5f839f790e924df081255b0a9c930c80700000000002251200b541effc0522207e5284a26071741e2b8302a964cb8b078d24540d73b9ec59430c807000000000022512041a1883aa113fbc5bf69387e0d599eef86181f0c916bdb55378d907291702f5530c80700000000002251206c2eb1f12ce37b57524337c6de7a55ee420edd100d6cb3ea50f512ef68d2075b30c807000000000022512090ea942f7c3eed7eb073682d38202bcae9df106034a3bd406dac13371bf18a0987e2d21f0000000016001471c1c386a4772bbc7f39dc7c7e75a17ff5d1e92402483045022100cbf19c0563a70378e26b5c9c1e2a77e4783f8926717457899efc4491bf3402c4022078e1b5e4d759eea100b3659f8a421866e96ff7e23e161c56dd979961e7b6d205012103bbcd5914f15887ed609c6278c077241cd95f80dc199989f89f968ff007fe8c0000000000").unwrap()).unwrap(); + pub(crate) static ref TEST_BITCOIN_INCOMING_USER_LOCKUP_TX: Transaction = deserialize(&Vec::::from_hex("020000000001025571c4b822663d52957d3f96428d01ad974430cb55cb74a83578c307eedeb6040000000000fdffffff76a187b1500d30febe710cff9d1efd9c96efc6f90e7369449d4f402d52d9dc5b0100000000fdffffff024e640000000000002251200f5833f74456e614598580421bdd759e9d55ebe5b9d9b2fedd7e64a5edfca5e58e1c000000000000160014c966246b7e79c0e229c252d57d3178d6ada7eb1b0247304402206dbd11aabd0f18496956cf570634bb34a909186b5bc723e249f5e1aa38ce603e022018736558c0b621d87d155a00438421fdbef8084665d56fa7b556b44f09a2153d0121029a7d0fb77f521b360d8e1a6f83a18d058bc7d79c9a29aebfde844c5bdd33d0960247304402206a315b909f4211e86fe17872ed78672aacd0a1d0c1d4492951ee8147ffc9afff022014b28f9d460d1cd729e21f2ee6a89c9d9b89bd7d323869dc15bd2688680088cc0121021ad991d43f2e7f4e2609c92d2fe56ef750c4882908395e4b375d4c91b8745578986b0d00").unwrap()).unwrap(); + pub(crate) static ref TEST_BITCOIN_OUTGOING_SERVER_LOCKUP_TX: Transaction = deserialize(&Vec::::from_hex("0200000000010138c6fa313e9beaaa692ae8e16c15fda20f77a6b2a7f5ea1a019b7dec05e232a30000000000feffffff023f53060000000000225120bdec156ffaa03faedd665a5098e5213cf15a1c8e6aef8ea61e979e3f9e996c1bcb88010000000000225120794593901923732756bdf76e5e3f1eab98211f57f53255b180dfacc7df080d090140bd421ea53bc8c9861f27a91b592f8082761a210111d693e51bb0fdd131ec5760ecb5a91e0081f1a824c1c0f18fba6083e2cb6e3c736dac001ba446c3a33fc608a46b0d00").unwrap()).unwrap(); + pub(crate) static ref TEST_LIQUID_INCOMING_SERVER_LOCKUP_TX: lwk_wollet::elements::Transaction = utils::deserialize_tx_hex("020000000102f7c93c63e9c8fdc988dc4d18a384a47ff0265f69115df3e1d20aa95a9088b20d0000000000feffffffb7aaae7d49228cc9e6f46ea1ce3a6f4a03211012ab4404cac75bf3071a2b2db90000000000feffffff030a3406fd6ef96fa076754dbbcaaa2275f55d73b68bf89dd3955365045f28a483180886a823c501cff34176e24325778050e2895c09fcdd209e4b99db10386b945e9302a776b3fb3b794617001a931cd03b18447924a0fdefb6ed1334c175ce8824a0bf2251209be19949a8c3c7b615bc0753e4bacc5983aa81ebbad7000b748aba9aeea55d210bc00f0310d701d646ec8487cb00baf8d3a7b896fb17f3a8f991951b2230bc2e4a093f519930a5d889ed5d647f1f4175b61260ff7d5832fdc821ac4e9dd94e24a5b502ff10c4ba7159c8ab268e008f451e2ef2b2da6248ca80c0443236ec8592d99eca160014bc0895a64ee1245460da8e62dbdc4673c1e36be6016d521c38ec1ea15734ae22b7c46064412829c0d0579f0a713d1c04ede979026f01000000000000001a000032fa300000000247304402207685a5cb06b785a42f122fb18c32404917b6130bfb777ee7980489f4c58073660220591f7405636b96a965ad4d635d4c19ebb884f2fc47507ceb7b32d2aa9c3d308b012102ed6e5a00131b318aea52964725020f587533171f3065b17d9e05ee869d99cae6000000024730440220502c271e4446f8a512546da44527aba2dda6213efb9ca818d8536004ee95bc4e02204931eee4bef0cc1610c673ba0a88e48e5c09664111a9e94dc44f4d8f8ebef4a601210221cba62879e622ed97e24b67ce0e412ba2d127cd44aaa9b6b930d7f9a45f64c20063020003f5d8d9e0632f5f6e2a8548de6bedd07c8b3b22d44aabbda0a73f49ea61203cd128c6e26959f6789f7cd2a71de6fc4469061121a8e4278636a599dc1fc41ed8d65a0724d47aaabcaf2f5d73356c314796602734fdc525365b5bce3d9985bb8276fd4e106033000000000000000192e62001baa5af15f4a175828e6c12e390879dda6b92361941eb3b01b8ae82c79b1e832fba33d800f3d9d4f28e1a07e383ea85dd8bb0e9b368517ef38c046f76f73434c0f251e6aa0a67a17e810bfbf001ddc5c9a28b2b1663370658b7f44eec215973620df91a7c4c851492e01efbab44c8395a9a2c7a178f036825e1167a45d2f3af1f51e0c6e0e74b750e4159305e8ed5b4c90810054c07ef0b33624b2c42e76fda8ad44596c0cb06085be8448730238950b8a497d7c2346203594e31c6de71320dbc4fc7b9facc0bcf4166677bc278860ad2130147a6ca7d2c676cc893a4f3850396b2966aa88ef495c5cb39c10dc1821c1fceea048426dbfab767daa51994ad4c26cbd4f2bab439b5b0fb68ea06375f8d1232d031aa074b25e72c15b08ea2dc9155ad6623e299ce0a65afb78c260c0820d6753e7d92f2e0866d573d148822b026f775fc7741472013bc55e5fbc42462851a4142c39e49217be66df3e6abec045a89f180d5d8fb2e615cb39e45829b50c72541ea27cd17d7992eeda754b69fbbb2e6a93cca6dd7b57a62736312670b8fd80f9543cbeacd4dcddf3a5a2a9c13b67bcb96ceefeef9b385971d89995565beef05214c2195fe087ce1bd252d82406a941d05df44f74dcc27672a0ac8e9d54314412f7ecca78c10454ceb69777a71a95c525add665c094f70d87116436139b045481b220aac89b8f125662f4a628f244004f1abc807b5f86a7a39785e5553a20593a27718cb6b984b8926fe76562bc71bb2f10475a449b61da9198b5357d6a8eab9b6cf936676c1faa241b2d97ce3f4ace35e9b9ef4660cbd307dbeb492dae54cbab2a5a2be91fe2aa1571c95368aec158aa95dc080e78c888faee9792ada2b7548d2ab91de3a6d3eb247a375635bf0967ad18c952dfc1a3314e31ee30636169a0d1460653f544e022b92ccedb4bdf47497eda48d4cd5e79a7111a1bd19f85c7eb69d9988b4bd9c62d69eddf6469b0f8fb62c74ab82f07f9a50f472451ff126fedcc58249fdae7bb1cf1c3248678e3852cf3e119faddad2d7c7cf6a07dec3d3bae19b63ac46694b0e1212623e285b06bbe5031dfaff2e7c8fd912b391ec938c6512153f2c57cf4e8eccd2fe949949d342028b55914b2c2794375e9398a06d4d7b2a291cd891530b2cbcb1c03558cd8945b66066565e1ffb51f50aa9ae526553c78156c8fbf03388ed520c2488cea5c71b5f7f8c337c1c676989b01871cafc966530bc37a17f230ffb4ac0246bc7ba130b7910e1bfa32eec33fae92d517f6776d428012edb87e0bf464c4694ee9d5caa0927e709f8b9ceb0a59dcfe2577e4c65bc09a9f6fd7ee7331c9a1666fc225e3b6d5a5fa73ab6c73f44138a20436b4fd69a0bbcd9c77faccd347b2bdbfa3f41e678782a533d4f5fd5b65bde843f4bc65993b0137997118b4a861b2eed7ed01a98df3a0c6f1cf75204376ecbd828ce2ae893c82d84d654695f5acbd868e3a766d9595cd943b8f61f98b33441f250a5e8ec0a777d34bf70bd29b1734fcd5db0b668254cdf26a22a0361b3f56d5d144211bb966e54ab333a8a7c4028f253e6898a106933c133cec01cf3d5ab0a1b684a45df4034e58fb5c62bbe175b0f10a138cd79e54d5fc4559437d08d11d58081232cf3493f5990cefc6640bae42999844805b78d79879e56754676cfe595e60ca5c4cf8d56cd05ecdbdcc3c18b68a2dd8b3e3d1040f01f392fd683552c4adbb7c202689d92a056222208a1766a559dfd3558da1780d85f14bfc8fe8c7400511d43d65f67a55eb376871b90d65b958035c78f00b561ee67d7138309ba1573538c42ac475a6290aa33e47fabcad571ba844c2fd93823a3a1bc6f453dab3e3c88eafe3dcfc212c9433138ab4ecc808a15c15654776cb1655efb6af3b521acc04bd94ef3860cbf275e06d653cb277dfb977712fe2c1eb342180e4669a49d7c4f599c54a9dca7337db166d4ce46a9a37f6cd54397c14e7865418fcf382d640c5d87ca4e309a4283d75d9ea71939cc2fbb98c59a19a46c60b4a7497e71c5043731d763a26c0c97905a1fff6ee2948626aa85e943a1e777700997d5d5dc94c5a63a28155963ec33cda9d1ed501f38348dc20b98448b7c5a943af2beb77da91a67f06af908ef646ceb2f4da59cbf793ec9461c938cf2872c9982cc33203d89f976c6fc4744aa80bd356805cd9beca73724bdb0c6b995d063649573c4ea8dff552ea79d265162e8f8a8f6ec2ae41186bc834157e70ea69d192a5feaced79492cde0da05b645ac743a6ce3cdb9b25b147fc45bf782545ed498b7cf364b893c58b47ce5c3afb9cb1d90cc8921dc82934a03f4f5029001aa29d3cfb5a8bbbfb4283a4410bd0bfa8b7c008143a1653e53f97e99b6283b6417fbba024ea60a6186762814d6b7313292031f062e81c56793b2163b868e211d62c5b10c24ca79dc6797464848001154fd3ad1ed5a421fa8b348fdd6121305e0a279fad5edb02a1262b760155dda58a9d31cb910bfcbafa2091360d8760d0ec6899833f3067fd1692b3e2688bfd181b004ab4800054132e2cda7cfcc9815df3cf3bb7bd61b940323eea9bcf8ea42cf0a7f84e34e193b33dd695b159863af3d4c2d1660d396f1a09c004cd7aa248c6c77a513863ed10e6e363fb6f5925750a237e00f848c9b04783be465aa4d4056f0324ea86ebd89763f3dc4eeb90841ad2696a8fe290b90d1e70384b11a9a4f876204f5190c0f7ef60c39208c8755ae3dd2274271c37c6e4e62a71452dfe4905648e3efe15cc7121a0b48905ceacc0ca3464aeadb0e321dec08e898688ab115da4e5063e907dfeac6c643dc965c393a049b537801fbe917b7e9aaa3fb038d4b0187ef8bc2fda515ca04bbfd4eb3fe37858edd2a41cc1e261052edb30328ccd8a9f20100633199f8822c66378bab0356fc19116366d94210fc42bc2a49e390bf2b32107910a57230bd11be77eb523f41f7f81c9cf65328c07e6a27d2bd1f625388dfe3752e98b6aebf3acffb6f3948578e3221f74eddd8b3c2e83614ede7cfc4a4a515b083109abf5875f1dbff80e64275d20cd91cb119a2fec3f75a1d3305be7d1316cbaadc0a5f0c644f437d856a982c0529dbaef3bcbd9d97c253eb0b0c69beb0549bf3ad8f9f5067306df8b601700e0a045813b9524e0e713fb91765dec26b527e3f9762ca6b215098a12c388e04ed28f302bfddf24b15551068123b8e71ec08bff3769b14e057f6d86a263529e85670a61a05b213e9ad30b75511c9f339a1b96ac9ca34277654c168f1a870b4b10f2305d177491dc2ea492ff49067e39f7195b658fa4fb97d550323d601a3efc0dc535adefc3aebb2505b6142e8c76fe69f7dab1aa4e6a0fb723af5c991483b265fbf05e8ccfcbc9d60ccc349fa138898912e9395f214347ab8ee6b79c11384026bf5925c5437bd80813b807a8bf6241f875f987a14147c395ae8df2cc74347ec1839d847f6a7a963910f4ba16fa0d53e824ad81f4c5d0cc54af92ea8ad6a106723ca0afd85d697d70960ecd7ea32909ceef20a59b744d00b171bb09b91cd6fdcf8728f2637b46c1207e844342f77f6d0b82781293a36111e61ced148ba8342f713a3df115cc97d9c6de530bd47376c429f41341cfedb8a8f160018ce4c250b9f4964cc47bc1a3527e86b257b93b0e62fe2a9f545342ff2a82a9316e477ede3fb429eb291fb46bb4c33cdce7e4fceb0aeb02cf18a8d9884378d9ea227dc52f5c2fb0bb6ce3e82cd9e7ff960b4a35b9e925b104288953c36e74764f577fc9f221873eb77db079ef502177eacd423321c5fdf03558e4a8b525378c7087897d805659f98b22792b5cb4208d233702853bc90a4d4e1d0fe04d45040a4d08547bf950b97c4771673e6abb440887087caaacf9bf524646309c3aaf0ad5ec22d0302a033596ce5b23f68d14070d3fc0754b58311e6699813c89fbed8582040709276f7416b19288520a6d8e640ae3ead1f02b4e358b8f5410cf14dca5897c60ec2370233f5f7a0a3ba5a6418a91c8786a44fba74d2fd72a17e6a810b03497cc502ebf2d33d54d2645a5847246c4d5ecc076f651e2fe61d2c8f1b53cbd604d79784ad4e79c8e3b2b07adabb2e20137df25cdfb4a86ad92efab93cfec972b86ad755474add11fc56a2cbd323ed92fbfd655e5787bfcbd4c44dd03932595632e8d34259b18a59db12fdb724e9586ca7fb6c06ef1e4ed03300c5b511abba480567a18147805d1f84616444e04bf2c238d9b70dc856878ac38855169d3657666f21acb327d84b4dfe87ab7567c2b74ed5b5b9210079e805195d3c51446d3088137eb4b17110d4263841078b1c5ca603a03015b7c51243f206b8718e7ede36b7d2308e194a46de3f7668178f318b5d1abc9449b7db00ec488fd4ac5c6b6a67d375e0c71b9de9b69f2b33f53a33acb3669553f77b7bc44cd6329cdf9a5c82606c564c8140796ec9fd0cdf9a628d3599125303f58f386a52728482abf5a286e80558dfcddc1bf3fcdf4dbb958cdec81a6dd91a3f103a78afc3f1eccda7483acb63f4a60005988416368d20f0ba90843493bfb994b3c52cb831b8330a09354e76a10a4d272e7ff5c6c449a3151a7f4a26033291c6b0ee9a0b4805b83f00b619862154e536cb18833952c4669bebee0ef2c45fe04d4263d69e638d3b720edb19d83fad0b67243cdae99db06d3ccd4c6aa0154f773af9fb762e2a39777df873e31ea28705cd9213a985092f68e1016fdd6b4d323b0609840801d5730601cc8071ac8c4d968699174c2464754464c8158902303ec1318132e678dcbdd137da84b573c7a9252d65a32faf4f876ee633572df482cae6ee5586cdf1fb238590b9d4320096d9eee3973e29b42079eb108d3f777267dcc6eb02e948e4d47b15187769f0d8443b7aae30e1fdb9b16e75d99f3240d2218efdbc785ff2c77620cb3815efd9611ba03a44530229f41a357879cdbdb5b8ae6f3f9276be87b53b5973b87cf71e697562e23931fe150ce1d43429319bf1fe581d39199c2ac746e8980e3172cae16de0f701b19bf2b282043dc99f44b7a8d6e3856a5a7a3bc7d4956953e811a65f219837b4bde7809456cabb0eac70edb47aa94e712a214c4349e0a66b46e0f26947ed1e8729aa85ea4dd99d90a4afd52680258ebfb07ee16ed311b11498d07b2e24b3b906292164786d3b8b210ff7f77e45f1c23f67220f20d25004a0cbc904db9edc5b91c653cc7694a044e3312f6b184384dc4b18838cfd3d5b42dfe36067236663a7f6b5d764f64cae76366b41430951f79ddbc360f2e811c6318617a2eac4292a8f9d3f7d18d7d22a9fda88976ea34cda280837d31bdfd2ea9e00bfabfd33a4f28cb75c4b71dd3541e907de1f180334735e235d38d5f044ec057c03da20397d5eb4abb261e36fc57426ffa885c6306b4d25f2065799cd6e466ac7d082948b87da8e3fede75dc5872e2b705d2bf603774ccb44cab2c733add2f28343e5cfae163fbffebfdda4b6f7b129675fb2002bf3c92c8955b9418add9a3456d3909799be150b0eea48c514b2d693dd4351832abaae2c916bda7222747edc36cdc4d01fe26cefa4966185aff6ec2dca367ee6ec6db90cff19c704757d3945e9dce04e5d00ee3274f6420b51f48fe6fb02816789f1ec2711935aff790bff124a21e9a5c0bb18cec578d9bdc75283b982b0a6a94d7dfda2c3db9dffe6f0e2571b6932d1c57c713778766dfbd65a2caaf11e187a212bc9fc711c62017cada170d7b6bee0b740c72ff97871ac2623dab234d55767aa2d8ba611428bea0cfc279db9fca2390e06d90292522c9198adbab0fd47737944dd23f350455f30912222a6481a42a1ce48166707c00908d348744ea74d7be7c963020003138a553bc8dab03dfa0c7dca60aa95bf2ed80d56007531786bc2c4769424a031614cecc622bb51dd9f37b88e0e9df85ac9df3e33b0466a1c8aa4dcf4413e6eb401a75492d730fcf8e5495f562c96801ae204c2b24e9e20667aab57a9cd563a72fd4e1060330000000000000001682d2c01ae222e5be2db5db6b622a2e35ff62e9b5bd1d6c001fbaea9faa717b03ce8def1ceb9ac5007d29850ce837edaea1d7617a68fe773f5674f3aa9737b6e64f831904d3f671d54bf45455fa2e8a9d7b3fb4d948363db561514ca8a0a6f264cd63af7271f19986e8197fbe4cb5f38295d06768ada8ef9502e3c185b3b190ee04e9dff291ffc601f0f52eed8e3c746dd7fafb5f9c8394032bc34d91a19953182dd934f769cfaeb3c0d8322f177557645536ebb82ae3e8ca430264ec653ab8957c506606e26a036a2c242dcdbf31926e02ee28844b398990bb98e908d09b97d030fff96fcfa03a4b250c4027c6bd2ca806ec722210bd9bedd6c5645b281048762016ef76679f2c1812edc428b8832afc4e857de9e0b36c9b7bff1b200d6d26214b77ffe470f032c699dbddd909dafaaade9969128be72d6d6e7a2eaa676a469145ce7a167f877fdeb2af61fe4fc462a7dd58e17a06a91d6c30074250b703ef892366abfec0dc99d974fe527c17e939acb0c07027f21125e2e137c9f8f2c7129230a89424514ae17de3acbc761fd9b6215aafa30d82286262af6fdf37b3eec9f346ef0bf4e66c4c7ce0461bb936f8394bad59aa6c573bb9cabc7092089b33a628668b9346e48813844408c27f1f6aa9f116c16767997fb0c6d55a231f71ad6f11e5423f2b4192353ac043adbdcb19cce5ebbd4202228fb99a4e2b03e3e6bd6915ae84d7767f1037d27b814562f6f452194e0c4a9c580052ff77e12e21b43c8a197ba5237e0c6b7019c51756b4bb5b79e4868971209a5fe4adc1b110e50d4d5400e000ad010243c9b0d2bf0d8f9a07f96421caeda6fee7b777ff2c7f2b14703c2edae3c968cd06b435c6a6b1f5c83cd791369437106fd8cca7ecdd0221c2d264390ebe543730cc85601893456704a40631c302145959edf86179893cd1f063be7504703dc250203ebbdad0c5c12a5954307ed73572c15e041833a052af1ab1247392f31558bf75c18727974e833c6051b356ff3e96902c346e79064ca31605d4c8adb92909f49da9da8bcfd5fb0b60606424587b344ded84637fd5df49a0ee20ae9da824ab6a2826bbcc981040a323597e6d7eebc2437dc29aeb26849b3b496df13eee71880c5775866ba14b8cdcc3e68e57d71317f00da25f9f8a8c7276f3dc4c1018da07e0557fe47bbce422ad3d58e8d7b3255865decdb85d8ae0dbc912f306605827abd0a839ec7a94e19481ce1b7d933366948eb3d8987ddd65f7980a064eec18938be183d65e48c6c4dcfa65f1ab0e1dbda93ead2ff0a70a33358dbbd7d6d9fddf26c9cfb02155f1225e93929d63f67409c0e727d628cbdcfedfbf368b9576a1515b637269bd98e7253e2df3a65d9f4605eb7a29e8d3d35936bcba2583fc0006457ca4d9df6af7d5d1bd49f87307691f8e2ad2c36ae10fd0948b0335306747de7fc7dea88695779d600b55dd55aa82729ecca629ca3121747e44bb82ee3216800440e2f90702b03e88fbcbdd5a9018785a84dec06afd0059583818c635fdeee877c7c338c8a1d7fbfc91b02a6193a8f05174608d64055c49995c2c4d00262908a1ea5b99a1a9f9557492b4bca51b11fd4b7504e842d8d3270a5d10dc861584a6baabecf6fd9180f9d36b55c6a4918ec39986aec4ca45b804482e2fc8a29c6524b955038f7f6078ae655e645fc68a1da71d0c33242155477bbca7e97a74c293cc338882b74cf1976dcba35bc0b7dfa477ae70bc4b55a551a42ce259fc82db5a6c31b4e76f07f63edb82bbc166ce37c7c3f9ac5371ae865d986d786650f4015ba50eeeee54a7322d9145c6b7bcd660030d6cb4f3be63177c8cb8ed450e671807d1947c27104b82b8370e8e212370ed63ae3ccae1482a560c91db05f11ec56b63c1e1ab4a3b7beed1d9535128f64f2a2512efbecc4c222cad8cdf3ac1cb737da23eb36ff61d1af7e09d44f0a2c900cf106d006d0c9de8da392a3a45e965bf5f5ab7861333c9aa4c425c9a974ad23489e7c0db9449f6eef19b9a798321d07f64e740e62e5669ae281b53cf68a6dd7d843db42966748b3e535f65bacc804fe729dedaf1fb285b9da83e01f3ceffdf58f94500c4a03220a114b2b78fc2bd3f9ff2ad0bda28420155067456a842c75bf1060a238638eec027374c28a361f3b23b3733f0aab137233d13d25e9994b6c5a1cc180eb411165bf2b30e029619d1b9bbe32882af95afa9c8bd950097f7a75b4881f4abd1e9de8579c069ea66fabba2436f81b40ed4b982a0484431db773fcf6fe5fd174b7b8a5ed8a90df27bc6983f0b402c223cdeac9084a433446322c5f4ca0aeb57e0dd0e478007c608c6032722219312ff92b48ca536922e023d00ae21819236dbcb5f264a7188ae0277d171ab06c519200a90307e318984588f4739d43fbe4069180bf40c8daba44607caab32ab9e6d0fc8553d5c26d841aa6cf27c29f2ae3ed99f2f1794488e7257237f2de551d83deaf201fd4f043b6fc2cd3e176d4cd8a0f4ff13304b55203661d0a2f6f1c937190745648c086fb8fcbcd9e3ad93da5c51c98bc8440c145d93ae7fd50b7eb37896817d8e7ccdd4b8499ac4a4b59f0692c47bb6ad3a055300688a5ffcf57ea88e7a1b66160f267486c25786235dbfa6d3abe77268d23fb8fba4693df0960208bbe5d4376d0bf2e7c7b657680696cf1cd293524df0d618a4b087fb2e38f515ac0a6414eb27acbce127465330c00f86a065c5266686d8fed0eea4b82ed06b8b2e3696e9207930c8053aba146c97c45d8166ed98bed9b113afa2c42d017d825b5b16e565dbbea58af1b2c2fabfe4af80f6fd1fa6647f36f6485168c82fbcf2ae6a9893112d1a6df2091e30fcda9a52c58fe5c0b75cd99d78e563d4226ac835f7b3402cf9eb99b3865a4e7cd3fd48d3c38e5c0b7bb1b48488565f55cd3402dfc35064a7708be6afcf45485d7e3fd77bf7fd5a3cf06952d24f385463eaa33bb65fc6af35506f983629532926639de9473ca1410546e98b394eef17cd0d81d240a301df4bd40c1076d79ccefbc6e176d9aec676b8bdc9d049f7f8a48aca9edc29328a424b0ca6c436951727361e51d1c6885de66cea530a7d88dcc652d38112452c8060b80f6d033e33c9902b1b20a971e701123ea7941cde65e46fa79ea31c63c6efb12c0c5299ac1f145b7033646f5b903a84c2747d0e9cb64d4c9281d29502f3aeabfe91a824202b81d21c7de0dc587e91c714345a1db6827c0eca7527d7578686a1e568ef29465a9e700e0bc8466fba033e6e706338906dea8cf13523b99eb20a2602f67127cb0208f0378fb78c791f2e7ff2533334620570bd68fc2decfe488caf88efb7d1ee6a9fc4cdeb73c188518a7d7f85d70ca2477d99192679aa25fe336acb45488e454d8fe6d840d37ac789587d924f3bb74bcb63b04874fa4d1b43bfaec120ba81fd805e7ea8e11d5c389ba42366f9192588d155d4939bb5bf40b6dad0acee31898985bd398451b1f4aed10397db493411bac4f6e1296c5b93a2e16d0a531dcd319dfb3668fd07a21186136b1d352512324725fa413c73446d3233766f7be45df69e6820c535eef9e8a4ec21737d02e14f0261c46e1d8def672fef00e269d6ccff269bee1a8575252ec72e0b705c8188a48df6f0480651215c016c4ab8fa8f606d3645b447d3abaaa0626c73fad7c24ca2769272da2da1c4c500981b0c2d3d233188011f7af8379666d2f19de2a06d4023834f52f651cb06715c6b29860812b7805038c5bc807210d1c967383317ba0ac9a1057451af34ace95c96adc1de19131e047d07194fbbc1bba8eb9f6f7abecf4a907504b0920eb76c2488ee6b221e4280ea4fcd6b2a5176e7d18a61f93434c411bfbef76b98b80eab591f4c4faf13ec6a90c286e27409a16fef72a0ed9c734f15d3eb1c1fa02f97f088f0d37de90c93321f332d1508625fddc8508972b74187508137df0fbe33f926a5c5317231e0445a3698b8af9fbc43424ee753976ffe40404265f05e150a8ec5ab2a66227df1a819dd5172ff84cd7818dc798dde92df796ebd1d707e917c7365ab0e9f3a77c80d7bc43129d0c7cccd9f26171a94eb460f593ac5688aff9a8b16b1d3a78b531db323dc9b92316ea4c22ecf91ec87106a78806ceb29a9325a278f3049c37e80c1e419f64af92c004b6510e586c41c2a1f470827f9ef54428fc298920436d3e1f4357b4912eef125d1482ed3facac3f65e7cb9503536ff13cf7d6007c3ce0ac1b37591b3ea64045c89ba0ed22ed915f77e978588daa210bc742abc226d472b0620f34c5ca99c1ea968d5c291486bf4a0520d7865540e85c53be1520cee2f128335ee4c49b77baab70e81266feeeeb3489764416e1bc0c370b16e2f2a9dc9a1b70f1c8b8739ee9368296c37df65d920067b5d8a825596203f5572466101bb4c2e7eee39c3ff861d6dfa9033730ee40958fd7667867f7610f0e8a26335aebd38f6e59808d55a56fa7ac67c4290c238a5bd5f8908c684817a615b8ca7ed690a2cd39264730e701be1d501f3b316aa92da7aeb4294d15a58511d174047aefe8b66a5aaf9e41a1115b718a3e478b9de656ff1a377523f9cefaad2d75330d732aad9394feb120d3f599b9f088999929046d62b927866d448ac11624d43d3c7867c23df8487a1bc8bc1d9ed32f704d38f5f212e7b11a863637ade688649fab390d50b3e5564eda91a9410d391f296173c88ed480b90d368291b7d8b4a2236272c83bacc56e0474c7cd7b0ab019ad2171eb800f315ea6f5d9c6a0a0cd84dcabbf1da3903c9b1134927a252924769e8a53f0f6129c0667bdc0e83fc6c0f7b1e430cc67e29cfb3ec0c03ea8621aa5920c1aa14b2417c705e997e2ab6171ee6025b2e8310571a7876a2fccc1d9e915bf3622eafb2570537d6af22286d9cbce3bde2b97728993e2a2dd036d31f6040b157070bbba57f1febb36e0f8754fd1a6f6da6a948259a1beb26f79443ec8007ef4b586ec0b77ea867c3fb44044d26ef642925a476e93bed56bcfea8f0e7197434aa34aa9e146cf66a368c4eb0a5e299ace325740a0a0ec1526d1d5ac86d498e015f4af7c8c4a976711154d8d7695b0db185cf9425cc6f49ee04f958240452cffa33dbb5a9597473297e8d6679637ce915c4845689348ad5d796adbec2996d7893600b6098d7fe43c946f7f3f65d415825059b8458ca6e85a01d94c3c66b2a3387666e560c63723816f6716fa2c80a1ef432f3a4cf481162dc69cf13ce238f86486227ca23751d5e1ffb69eb9b893237c2afdc36b4f348a9834d00063b51015b20a8f414b6439d3e62b8d9d019aaaf3fd31f2d5f29b7c7266a680b57ba74ef2dc7f94ec50332d775a0b5b00a16467209c9d1e5595ae4c43426804cf818e215916fbc9e584dd98cb12731b59495d1106b7c75ac3f004c50fb74de79dab4e71038f10c41948df65958208ea73827be6dca875d14995e8ae6a5d592b0200454952059c342f9eb4fcf31c44a5841f61b6a79de97856ff549c361a1ce6ea20eb43da2c95b097c71e92871577e3cf822b661d01bfb877ca836d2b9b25c3121d1392b30e65e3e7173595e087af07f3af1c3736551ce8a16bdeeb3fba1f3fac5c69c056f8aceb8399d9fff7bbd9dc6c11f13d0bbe984eb49e7200b28f659719c08fe3bf29c03c828dd81706ca5cdd8f670bac447dbc4803e4eaf91806a7eb0d8dd168e20abe2b7e632c1a33877bf45a673b47c344222442eb05a8125936db229f8f9bbdb52a8937153e4dadc7268185ce71963706cb15cffa9f482328e8f0cfaa589b2480117e080e31458432cafa5566ce32a5af0a5df8c60168c71a5b5ea3a2035c98ca62addc63f8590e7b17e461a3915dd4e9e18ec6f0b3bdab7f8e0000").unwrap(); + pub(crate) static ref TEST_LIQUID_OUTGOING_USER_LOCKUP_TX: lwk_wollet::elements::Transaction = utils::deserialize_tx_hex("020000000104c057ca341ce44c9e2e580ca66ba9c0b8691b6614d19faa2a6e9070fb26ad43910100000000ffffffffb3c25aefbd60e5594477108e49217c34d5b7045a13c2de425f90a4d08262cfa70000000000ffffffff31497f924303165ac02a11014693efc94b158d4dd006ce5f0b0b7faeef3fdf480000000000ffffffff5c32217e1f90d5dbc1369eb34a153735e0d562d74c18f610e84287787825477f0000000000ffffffff030bd76c792018d962ee22c80b634443220d5954138dc7f2145bd3817381b94c5a5a0804f2788aea2209fc324c38d37a132cfaeb3a2b488629e704f018df00115328e202a5c8970c2ec085227a5773e67038624b00abad99668437a44e45fa826227d11d225120d9544b0c28687bbe2d182c915ae7e2ec43351a17d1cb4e47804d4515440e73560b52f9d6e34d8b54e55c38e3981aefc8c23190b14febb590dc185a5ceebe8c22e609aa638eebc568eabadcd4030e3e4299022a547dcb3520eb3838ae24b29474ac4f0335ad1ae355380a129da6180ae7fde73afa549e8e41c2672cb5b59522453229501600143122fd042d2cde1b119be7bdcdc3d1b2c934751d016d521c38ec1ea15734ae22b7c46064412829c0d0579f0a713d1c04ede979026f01000000000000001c000000000000000002473044022006a61d46702d41fa46119d79875c34ca9dd2be60d7afe2231124db7013e6238502204e0149cafa2c8d6afb75eb6ea5dfa781d6e9f826d4c2c9ae214cbe7be9b77298012102418f21f2efe55a94f0deaa537ce1d4cc8a3dd32e0c8abe2ca9ec18d4dfced1d50000000247304402200e11216fbcb12eed58eb30f3b5013a9635b793f862fc53127fbfd3c744bc0fd5022021430d35bae4676dcc99000ee28a985bd186f5d395f695a1ae28d051f84e18fd0121029080d46117f1e5d3a0e8d193fbaa82ef7b5ed0ca1024e7136f3723ed535ef1c000000002473044022055a8a454ff491ad83e7cf216ec6fe091d5f65d8bd8dc08c0a8f33e81bb5d215502204798b393217ef2810f777c6eeca2819c1fa73a170abda9ff46a7e923da91ee340121030e9409ed7a96b63db1883be5604e438f3a4a4cd5a47372e3ee7fb4829864a3560000000247304402200420104cb02a8b869eb6008415570c7fcaa660aa9e7e93cc0facd432624614ff022071212e977f6e6ad0a916ae58b8c34d5a873f55064fabbf542b9b774d58c9caef012103a5316de2491f5b6300b13b1f9d59504dc36eb9a33242dd9cd501c868f48db66800830400071d1d5522466fad384dad542f2e960fa4e6750afc5da6387d3d84c5afea598a3f607452cb00359bee767a8328f9d62541f538836daffca9b5b741ae9f202b630e7910547485af9fa21a33ce8121958c035e5350f27edc0b9b79d9e9ce2dcc7371dbd5b1b6a6eca0bec7bca426d0514d6377584c73a636a306d5cc3bc9c2dfc1b3fd4e1060330000000000000001a6204b0042064e012f4a701f6920f96450989958d1c51a617701cfe292b9599fbb46905818e0b743ba16dff0c954a4a68040d6e63003cd1cb3beea903078b1fc87aad4d03053e1adb1e196273354c8d6e1389833ad343900b0e35bd0f376c3ea301b0cce5324d76d0c6cde3c8cafdc697e4fbd610ecdbfd952f52c8f6e67276481781968421f0fb865e094f220d11cb8517461450645eddccc4c2ccd48021bf5f526a28cae85d2c1d5e8f20c08cb3bb335af25b60babf2cbde3d55d52f72a9721f508f5414feb2fb4e1681bbe185150213cab5804e1d5187218778e8c1fa371a81034383fc69fbd07cf8882a12f2d40a69b0719521bc0a58d8c65fae966c1f8f001a39765519a3f3025033685c1460007ac5f8c04e45d245ad8218c3a15087779694a23f378792960eca27aeb1bc4b1a638dcd8224cdf6b865569b81b69b14b8752c178c61af2874e60b91d644fd9636feebf581ad1798e3827d5d169d2456e556f97b324c80caa2f6d75919b5729b050cfb847077970c53b20e6e0b70c9d51792a7f625e68d9792f7803f3dfb27e3ad59b28fe825356ffd455bc8ea7f3b5d8034eb52de70de7b12aaf80ae74dd3bc8223c2f33b14ab0de0d0af59b5f33d710c7eba39468ce6df129146885825f92315a088366b2254a9d62a89d5fa8f675d76d03087aa89fd74cc93ac73962fc42257c32c2f08b1ef44895d3a76d9f7a96170428aee8053199183d5dc0f371bb9901b832babcffefda291d11ea95c82af3f3378b61a16bcb6653bec7bfe02c534f9c4f3893e249420e42dec5ae85818f459e02a6eae754f66f601ed160b0cde6d6cf94bf6a786ea9c0e1107001e39717f68a2a3ea6f7ceae597ba5fdcee6b2ac3a38994ad0ec981a1a277c1a0f8b0ffda6fecb1ec2d2916c389aa50b784d62e4771b4361285a72ae1bad3fe7b7d3de9e65f4c4efe6a616204d8a3fc182b7a965c39a040aef8318ab783aa18b17018fc4186f1a5d49e13030cf90f3146ab2cb3a3259543342fe7a2c4824316828da2a65ef838dea9c8d4d8952c2e0d0ddffcbcb2b7db8183926dc3ae710062a982b0b11dc423a98ef0e75aac660e11ed3e926f2476391386cbfef18f4e0efb9b4bc372915251e99580db04f98fe0a6cb3b801f177f42553cac1026e39a4318a6655deb8d6d7a2a6acd1296cf83927a3d2ee8b15ce90d41a3896ea0be685a69aaf20b90c3a49f30ea989801aed514474a8e8f307133bf0cbf513c07f841f494a891d54a88cc57e79f8a29608492b7e36b48caaa682614b7eb7ce7902d8d55988f52b59c1de7c085602d161ce169551ad53ec47c52854e11574741466cb17cac7a4047bc0d173c13e1c204525e7e0184aeb1f6dcbfd84c9bf4eb8a2880cdcee87495a504d45652a3a30851845928dc027957585df5e22caf29ca6110bf538de2b30eaefaaa27a40e299fdd830efa54024729883bc8c69614318bfc2774af43a6bf60d53c32bdb8869ff42a901be1be631c616b26beea430334355617cda9661351bab7936235ece081775914ace312756b8144425cc64244ba7d875fe959dda3af32336666b952205dd6b476ef470ae90a8e82fda57896b7a6a9b3b09c458385ef33f282d8ca337e7c6835f3af9e5461313b9662e63758d6404bb8717f9d69de87260724b0b6ed7021b4ff66f83fc9c6451eff118f8bb5de438e3b599b85a0f65ee0ac8d8ace209521295a956b7b977207f6c210b6897c2632b05db803bc0671b68fbd26aeda97c65d8e33f0d9d6852073eca593dfef539bdc188f7393c2e7b6483c58ef2458729615f7c4402aa810c1690fde4dfe574e57ec5496449866bda42c81d4ebce8eadc9722000a63b9f4f2ffacb8128646143ff3966bd2044aeacfe07186bbaa987da90fbc58bbeff99243c9e8b0b9c39b6a72ea0f52be4647c332d9b93b38219186bd5cc0086a1750a94ebb523600464b6018ef53a590ec6d3119f40fa31fc1644b5da8aaad36a0bca6690f5676df63f9f86f7ae1bef9c47af701a392795b9d16a4900a4191d333694408fb8d31933847f8227c713a8628a486e3a19da851fdd82d785afa201918d44835c2dfbb6feee0683432dc35f1e092069c7c75bb213b2b11eb9bb2a8df641e0f3e15e97e9a331a50048d33e35f9b46f272b46f370db25068955daaa115c48c404d6757816f55415535e22a143e2b5b52b2fbc7e0e853c1a4c970b78cd7c275fdd736b8088951c19787a60ce4542f99d01ee70cbb08b9623cb0aa965acea04f0fe8d3c4c4fa50106f72910e37ea92a35373cdad686276ecddd2cb42e21a8276fef49317546ab98b71c558361502a96daf1198637f2f6033016cf14e29f2f2cf7d14585f8284f9fbc2ff5b00cabd91573090889ccba7de1d3e331f3c14d793335401fe1391c47132ae02cde4e68a5451e91a1e6ef54d0afaa6ce867a8024350636751ef8bc3085e5ee4f15da9e9fb3ff3ca10e4ef85508f4c08c61ddf9348dd50833f4d11922734ca2b5c24cc8f5b2728f9dd071ee98e8aa8470b3b8562e0de0bc8b74607298c675054d6b688db908384a163355b470b433c91bd416f296c531dcbdebc93f27039bc3daa78445e8ddfb922d445b54b061323869b571c0eaa6f3fa3bf647d9c82c16ae07f9a369da43b83ddf0f312137ac51f703263e5084d91cb7ccd01607315cb91aaa15cb6198d95d8206cc561a008d9581827aedc591f4f37681fa59c1c863a3b687b30f16a6fc736e53a9a81fdb5ee6d78eb21245a834912c11c0ca91937a66c1286bcbde12201de3ba077176ddc170dfe74a10f293cf10531587fa07c6cf50c98e700edf6922d728ffca09912e3d04f853b1e5636b55aeec799f3df79c32cb77ded8a31ba35367edecd9d050aaa6913a4102a8c0d6edcb39311cbfe9172bdf62e385e1c8ec7bb70ccac239916961c643477b476897fccc6c3875bc4b5ebc6ef39fe1fe93bcdd5183434a1d4245e86e9755fe1b8411b1623476bc44d1ce93ce87bc6455568a518b589d163d918bdbc24f62f259413085c69807626caf8fe0e9b4d781b074822500d68b33c846422b7137b281c5b340a3fa3d9a509cc791a29713420f7ebeeae68852a5ae8d7885441d9d43b8986a8f84edd2d0c359d2c2e5e1d9989d042db1df48a0b5965262a9c084d05f4f47d048fe8b25f0ac250984ac1d9518b6114b8e569c271ff40373418cb2599a943d65ebb0f62258fdb3bb9efe304b5370be1db4a1d43d3ab1373330b0a03731b0f2c3c74b1764c3d598c81dec0b9960dde0d590d5e00289a6da55a0b9924448b25b091222bbab393f5d15aa6404e05c8534a3099115431dd8ca88b18112ac34a44f023b8cc2a6c545fe5f84dff50134461e367ae73e34c4177c2df9e002cbd9e0a3e8d8001fa0a3cf6b9d2ed835f1b2a65775f272cca4d84588f4f9b699bc967d9f6918434dcba5ca3d85e78e04d9fedfe6ba56e8064626bfbe6ff725592beec16d92cfb71f45d35f6f14a24c174ee5c4d55a72d18c69e407855672a27c5f0686b6f4508d13713f99ab498b292dc10ab6a42d1f50d68b6cf7c2ec1e0838db3666eccb0deba9cd7d1c493aad68fc88aaa7730efdf6ba1c8115f55f723cb05bc4227eca984ec2ac648b6a3bd359d097c2873bead7837f02ed6a25d77b221c1c166c44722aa515c1d4049920b68ef28d849c449a50e92c7eae1555d6e43e1cbfc05d953cdf87d7ed89d359a95fb21e9da1847e96ca92ffe8ea847c7f646a6dfa9d4b391ab8bcfc5e013c95eda7962e0160e346a578270c578e09a3d1787e5dc89b40f7175432cbcd50e06c4d57bed5ea20d812efe3b6a6fe3dc303b563be1943cb42964819373bab43d956fdb317acb440a566b7cda6b101acf53e1cea8f9e8b73e1805abc8c0ff0f5872f696e75f05f006c9913bda0123a3d4ea24e7c6d516e5e54c9512dfb09d8338ba1f5ef3e565ca2642a28e7a1bdfc28cc3ab1803cd03f27b166af791a38645299e5d1e3e6d325d71781e80cf70c90fd170d354f007369fd02f4644e2861d5442a1bfb720df2cbb1bb4f740b6f9bfc57161f8521f6b9c924fa73b23aedd8ccbc2c864cd7c97f27745a0b3e69a5b6a6a549dda72ba8dfd1388856df5f5e85ea4148213865fd0247f22e00d78e00f76ca99def7508c2b1221db118ac9d9913fa23123a8500bc5ac5a59015d88ba5c0f6b34d8b43bb25fb34bd694050473afdb70b8af4e4d87464b4b3684c03b2426331caa605c6aaaef1e7d4616c5479461ba6aacd13142e95d393d0b295160f996e501899654b842138328224b03e3f36ebfa318cbc3be6dc3663b49f3946b3710f430acdc91068c8bdb0fd55f92ca5ef44191a7a924dc1e9b311ef3000201d7796d945ed4ddbb5a7fbbcd63f603a7de64dfea24388037e37a5fa047e32cfbaabc7cc831e92a317b3ded781c77637bffe88e67ceb770c68afae93d187532174dad0c692752740756aeb73483868193bb4067ff6897ada0ed75334e9827eac1fed865df73520624178205ba1527402f92e58a65e8d9404649571549cc5c123db837c12b17360c87e6d0eab58d86c831b726fde172a95154789c38b60475c066670bce0a070e6f1f7b4b3200818075b4a5e90e78215dc35461d2f44e5fa6ddc1e06702614ce26c29c6f2d69cdb45721de105d60e05320d0076a35e378df3b4da76a6166fe1e899d70a853e9214dcd8c22ff87fdb4722f6f5603bd52558b0f1255caed47a8b473816f28702927c06b7f14fc416f6b819e7c805e4abd3bf9a97800a8e13e2617b683478f2bfe39e9872f4616bae103f0e99174fe0e2aa0c7d905dd77fe96278ed03c9e4ceceaa2e25c29e92fe6cd88e26296fbd681e4428adb5127898c5cff93079640da7d667a6a15f8bc86c7f7edacd15bca189e1370256a538e97c9c2b80ff820b4be71fa94d3333d10d913e49bbcb81640c96b02456bcd287ff63f74249dca3c9232b84c28b8cf2bd4fa9a2ed9a12f8d50dbbdf955a7aacd2aa1928ec1c7eccec1726e3a624cec68dbdd44deaf8b84055dd906cf0da36496c9aba37c2ebed0033d73fe256619a1652e56846f46c2324d65e38883379c66363bd233d491f1a8c70c46c7396f7359c35f8244cbc0c6676d53d9f4c1ee40c2aae1cce62e1668a3362ed6e5890085b04003c082362d445d51a60bb617465718b913488a778451bfbba5fb96bda7179ca7d71e610cc5efe532b766a05a988a41160df5a41a670fbf4b5e943a68d6ccb58b7443b32a8f5c3eb523cdb4968670ffaa06b3cfbfef2796b12e4c7950cf36bfe15d01cb43ab5cef529d5ae4b6e93e8cd13f8dd967f75f1039e92f47ac5bb3468afb4500eaa019653d6f59cd2194b4045bab544d93cb2cf8d9130494a346a48f29ade86c43fa02d8a267f80b26a82f90ee8ef4c05b8b2c1c169450e2a551c41da5b6aa32e330b9c65c0c73836a71dcc1ea3a08928aac61ec164394a1b4752e82d774f11fa1e50c047745b549a0888746c734056cb274fe24f0235a4e85df93e7851468bd52c9e1615c9549f6010493f6bc261a5c92690f3cd7f04d86d864a292b46a41df1e78505b9b2bc41c65f69acccd95bb4a0234e934b547660fecc572204fff78f6200cec28006bb6518c2ab0600c7ddb0108c327a2a33857c0eff83d6d9bfcbed78f13a6429f18bd9c09cd436ab989d09491b40db1d4f3f46d94a484e258ae27511468d707c858193e547060b8f60922123835ab59890278441375ead15cbec41dc3de7275b0cea6930efae4043d674c8c589906206f6255b40759bda936e85e013d9202f7b01fd056db4749c0887f8e152e45f8b40e806d9e662ba536853ffe49e5317d031394e5463e48564917bde8d4f9e2b79f0930c00e21da38fb90e830400072a18156bd93c3a781570bff6ff91ed25798c619b0e6179526c32d98699efdcfa352b115ad022a85d13b0a78b8f523fd6e26ca15f1cc510cb50a10043e2ac33152d7226df08ba328773519c683c7c59cc614babe67579886169458c1f1f973bfca3589a26a74211c58fc2e2a5d6b3f548b3f8bb2a4d0cd5d5702b8120cb87df33fd4e1060330000000000000001183f83019ac6d0581ec201c7212ce550b5a4519dca8113806e0f2792971210f6f1248f429294cf09ad67968a5998da95c420a6cca1bb843b1afaf9975a3799751df7394812045fddef2c75338b6f210fe1ddee0ab7605053f6afcfc29e1ddb61a0afcc13759ee48b80c14d449985699fa052037d976d37a6269c0f55c8fb3e68bd9d77c778f19a5711309be6fb89fee839ac04deb4856053a758ded3040135a883015a3960242677b8d9786a88d1b898b316bcc1987c9d72ddfc7dedfeb4ee011f1a6a9c315928c8d415e54cb0580876d79e31a9a62c3110ab849e2e5eeae485d722036e1e2a562e156aeeed3ad17a823507cdfd180046175d71c5b8a399306cb20c26a07af86bfeb98a80733c284f3ead0fe557661b841dd2179233951a06e6342754c00133e93b9b1abcab44edd727bec029bc51c159c3332c5e113ce8b4f9b42020b0663ce4fa22bf2c34315a516f68d48aa61546103669b9f0697f21425cd68be48f225522b6bbfe76e8d6d38665f35f02344ae2546eb3227d8d96cf44da2cd236579155efd792c2d75bc3d9fef24b6b91031e9feddd2b9172d52866a46df27c4c648d6c4a141189fefaa16ef2987f076ff328ea1f49ad602055749e4aba76755ae27dfc8b8bac05833c19cf22b0c859bb39da8f2e9431576c362b1c220dcd6f1f5a3e12d7856be4332dacbc79f70c76abe45a5d578ea1b2e5da847e21eb1a24d4bfb1f51646184e9ad3d69b922d800798a74e044586624922f591a347fd12eb8459322a30327464b8b3777d33c32acd923f78e2760089066289965dc895b25394b0989f6e777ed958e6355118b6c07fe72edc6d60a72c2f6feb22b325e7cefdd7cf149c89ffcaf574d0d974b02a8bafabbf3abdcd7add7a0a1eb486ab6d376816a806c964015f6b69152d197cfb4922dc90b6a1f945de43dcea62484b05bc1992ea5bedad7c21b588ac0030334d48f8521e49d779f8af965f627aa31d63846f69519268dc9d10075a8a1cdcf613fdd6604b7fc988988c7124dc89c9a988a756ed71a5c2e38f427afbb654bdc79119b376d28b3606e89e5a65590ba7a6a3cfde5d86a9705603783a61d4da4b4eb252988a73c7e65a0560d8525288a8dfb4079ddd3ffff81541057edd635bf7b087e8484945eebaabc4f9e317239988882b4f0c39196ec941017a859a702dfd0c6df87be15a08d6c325a0ed5856be386d8bbea88c203e0e7ee929c4b054975d0c318be4885ae604148c385c064588b4b564d247d6d5e8cb4424a7ebc0544a2aa7482c6edf0940621ea26e74d1caf4d5c0df2c5333b9d8aa4b17878c44e7e47f3bb942f8ea7306c2988dd27b47e4f85bf1c2e4fa9844aac375675716113d6606112b03614f192f514e5c9a42d4811cfb5a610f872210cd234102a2f04a1c4561f0d19abc7a874ddc308f23173ef98e8e3e142343157364bd2893910963fbff5bc2cd93c01ba0ea70997b9f2092919d885d1197138bec10230d1a964c701df4f9ffe4df726e6fbcea1a49de7409de76ae3b9f6ec27e2dfe0d5bf4ff28237d3daab71fe4cb0fa913afffe55fdae8cb1246a86ed88ba7dbc3275268d1de94fbd371325ba0b8bccd94632a185ec8b16da9e4d291b1870b0d789eeba69e01b25a5b25dd75d94705eaed68f9d92728988148a9a4ca273f2a5ccca53febf34fafe092f62ffa0698a0c5de8df8c0359e9e47aeaa366bdb9d1036ea4aa47a3e545dabf4712359c41e89cf4f39dfcc7898adce95513832bd26160d3b5472cad29ef87b77f96076fb69a82f1b9f8b39e7e3943dd94b7f78f6a7e7b488c30890c9ba71effcc4167f61eafe687e8531f759f96dc6adeb2157c7607ef48c6bf627dd92cbd0173d75eef5698666f32cb1d3f6d5b0169928efc0034b70eb059cae71278e710e98519e276ba3639aa41cd4bfb5e2cd892639301623394696f8bdfbac99db798cd82959264517e0c28b20704deab62122e8f35fb20ed774f9c05ef23373b77b0cb9ab0eacb654026a607504c5e13ad4adcf26ba12ff07b0642db4dfd9bffc0b7de20e02e1684f62f4649d2be51ec40ac37bfec3a82415128c834b608da0f06e6aba7087a19e244f5024d06a32b1351fd27fafc34ec7a11b94af8dd5dfb6db4788d0e3b5eca889a2d36a0d976fd7c5a25e6b97ff22aa3f0eae12a4663e0c9c9b33f99b75dbd80e16ea5194fd3ad77ee7fbd8008251baa8d75cf9e5a638d381ea5276f72d7cdc781cca1e95515fad0ab83f78c62755e4dcf204a751e1172a0b2f7bb5d95c792566b53f068ed1a318f443535c0633097a51d7fc5debb43b12b5fca482ea8317660ee19b6d04c938aae4612ff1b03900b630b81d605a229985d5785899e1f988717d4fde9cf72db4670582217bc98af826ea686f9b5ff625fa28a8a56e26e931feca25efcb585e3390ad34d34c2e76ca6efbc45f56127014d04d89dd5a1de3281f52548161c511d3338b9d344e738c9b683b0f99ed1152a279375f10f48a852739cd2f7bac8ce543afcfffce3a74452aff6112a1daea5405067556c55ba75b9a224280d6eda44429337801dc6e8f1b01dfb7eea727fa5c32fada0f344921c8bce82724269971c1cf6950ea842db86388217fea5f92550ef09bfe3a3edb803383e330bdee2654723ad89e44676ce611118a155ac412d52f9a9883bcdfc5b68db852f21fbbd51c5f2103820bc8e36418fe724b32a77ccb9f5eddb22b3d6ebd5ba3bec5498e9942b475280768898d25215c9f19502277ae6e7592b3263e360a1a63d1bf94a2e34358dc5a032ae37b0a0ff6b7f1dec514acb86da185a081bc39a4a0eea2edd3159c00c23d357ba845448355fcd85f7a1cb2fa185e6f612ceec79af15143d772a71256843fcc28168f8d1a0af45ef15e60c7b2b95619aaba83e4189397ff08a84b0f37c91cc61f3f7c29beb76b7b7cba682bc6699e78ea99becbc89298b306d1f8fbfb5361990eef08b52cfc56088ccbcc908091b19fe2401fa48d6ce543a263502681ba2637b3df71f62f6b4874d3e10a85abdb0b1578cec6ea5fc80f1e759f8f9ca4dd70f536b8a01b79c4be69eece7d7942b80698de60ddb8afcfde76613e8f6c617e75c5b9db9e80fcb51d5228131673a0124df6e646a470180157c72f9035080b8673ec73f3c1bc0547521ba878fc109bade65f90724c79e0e2e2ec6bb7fe319a4515d5c1a080254790662dbc6aefec8de5e02920151a165931a69d91c95d7bd4390de410d2c11c06c2d00bfa13b15676929385dd7c50488431a055bc6b3397a8de6b05ed3174011f47591aaea3d128582ab67ca009f930d59a8abd808a6eb29a93955513fba0c978ee2b91f48f999e411279e69b6befd820730842632d5f1af28970b3fb3d1072fe57a340e44061d354c8343b3080cb759a5bf5ab18dede45cecb67d0a952bca7d41447de1983d5de06ddc7628402803963e26832c1059bd5eaf7f47d5e33cdb5312c73e0afd06fce20e13c8a6c1184d39372eb5df3b38f24a4f748631170ee8edd3594070202dd4b85a76e6532fb6a34d1cc340bdf9ca0abf934a68230c570a70e15d47f4e5d06c0e4f09ed2fdc7a96fa58c497fba4104a44b058ae51a9210472a3aeb8429d584d6f654c1d6158c92b4413921e55e91193fe211755ed449595c21af177c50c06d7b056dfccc213b566071168f13cb24d868e1793720bb554f150594789fe7ea3b7a5e4d6994b49b0730cf22b5d7482747d41b91e498f604d53b9471b93466facdc9b47e39bc63179f454cf040fd9a008bcfb647e601e2c6df1d1424ac62b42d4f3cf15d3d1297bd9e0139a37bd8a1bfb8f7dcad121f95b8a8536ce896f1487af0b2ebaa7e5dfd36da2b99cdfbc07b5b05bdaa984e3b9d33c88d44ae1eb30c3d43194b1e239bd7dbcd869a92b636cd693de6f1853f9b5087660fcdd58a9e64860ffa76f88913b567da97952d55fba5109614b50aeef7455edfaa11b6779aeaa7586459b4521fc2fa6c169debdfeab3bf552f44491d3e943dbe7dafb34d54a18221362ba615a0352fa0bad114780a7d65305937f7dfabe2469153a02abcf353193be29df7717d8f43111c9c77ea2b875ee4f836ebea09f220fcf3691ada436bb86986489620a8c48a2b502c550bf8a41f3870705ab9980689a550e8051521ad3df55725c4e0dde9efb60430263bd11d862d20ce641e4c779392a5b9d90ec333007241f6f0be33a7ea96444c73d544308c4cae36e347079f2338ea12c75c187407f5cfc3940834660587cc33258577da30a4bb32998a66098235b19a98ec6efc0948b113b78eb8ff0434fcced1bc076ec697fea451cf50e419efeddf992fa731b088c5fa8374f042cf090bafb9d02228bb616e2d0583abc440e0b5aad6769bc53a887612ea19278ee41977ee9aaccb34189b4075d069d853a6149f84b675ea612e5c8fa09c1a11bac15f6ca3f1c09df5a5744909795a7bc39c685219ea9319e33f8fceab7fe59fac9b0b309ae753d0b1bd536420c389d8cc489deac3d749f06d7f8f9a135eac8f30545d5e7abf47b6a0f4c90a4d858e1bcf06be2f025913b0cdb85c0eaa102418728d36363e099b8c954d198075fd303b5dcb642ac9247c4224575f259b8ba530668639af03a456ee5ab506574f862e153514db9884ceba2b11a6a3883fb92baf3e610c1ad51066e979ed5c3ae206315afb9c6ac05bf998421e93bea58de0f59c57e82085453b4d5bb91987373175f9a53ca75e7e88b9f4acc0d6c3f767c08b9fc89df35a2d06f6cc0d85148fd6f95082ebe793716431b1cf38310bcf4285b7799c6976538608d567477790198b9e523a9d4c90d3d022f115b69948a94f78e221aafa51c99368996bdb6680602bbc8295a6008a332ff913a386dcdfed1b24b15c1279a117e6e55863427b8f0c0f459464e6fcffed99889d141aec53c5b4dd6728488247a4c204c6e8b37703a70f81e6ec98393e2defc92f1e9b15593e73b45a087fb6718adc6ce13b0071e8f1e6ee351bb22d3bac9b05d998faf485ad49a62ef6c518c5fbcf23a2857e1cefc7950c3ab96a75591c0a3b3655c207626777b10efe23079af3f5781662b3c7353b6cb28121a0c379df8cf189550c023f9b4159c84ca91d16f2e3cb54055a2eabfd4701cbd22825ea66d379bf157e36caca3c69ea42075f8a2882d438ca217483e1bf02cd968979cea2ad985f3b69c360da701e4f690fa09b4447fb2a70efc3f0d438b8900abfc7dd94be261497128ec4d16b550b781a6a732458e98d5517752ea7c5eb7ef078b045baf5d74f0c8375fb76b022d9e53e292cfb4defeca9e726c828433ea607766f499237d7e0cb46f4bc7678a839b3a91fce842e8624f80a08472760b831dc9f95b2cc7cbbfcc65bb2ae8df7ca947cd12c58ea893aad8a5a9b018033d1191eb15f1cd65df2d71ad65c56402cc3d460f800df4e8146f5131aafc10a0bca676e69d230c51ef2dbda6368c9ad3c585e39858266ed1aaa70fe6fa4d183cb87e4a29e6365f5346cf3e328dca541f54ce08e897d2ca76b2c6c2263872cad0de74254211f1c34d3ea38f4c8711ccfd6335dd07801218a63075f05a76573cd6dc8c69c0b177eb55c3925228b3e871fe6a8ff1c9d07cfd75d05eeeb681aabcd311cb69f3903745c6b92269c8e8594dbe7138390c7745b443f17f4d9cea978f9b6bf877ea0f835a948be8549b853d0c1b233871f46eefcb9e4bd481d6e6a03d4a36f064357899d1592fefca125db7aed85b236b141e66580def942084b1dfc3cd026bb09bd3b9f0c5d857885f8a9b355c5bfeb8bc04e9c97ff16b7b8a7f8fbe66d06ba6c351d396b245de3f10670d0cd69c5fca2e99521ce6a655d62c4f12d780b6367f3db4ef4c94dc5aaba0f270000").unwrap(); } pub(crate) fn new_chain_swap_handler(persister: Arc) -> Result { @@ -50,6 +53,7 @@ pub(crate) fn new_chain_swap( accept_zero_conf: bool, user_lockup_tx_id: Option, zero_amount: bool, + set_actual_payer_amount: bool, ) -> ChainSwap { if zero_amount { if direction == Direction::Outgoing { @@ -106,7 +110,10 @@ pub(crate) fn new_chain_swap( refund_private_key: "9e23d322577cfeb2b5490f3f86db58c806004afcb7c88995927bfdfc1c64cd8c" .to_string(), payer_amount_sat: 0, - actual_payer_amount_sat: None, + actual_payer_amount_sat: match set_actual_payer_amount { + true => {Some(10000)} + false => {None} + }, receiver_amount_sat: 0, accepted_receiver_amount_sat: None, claim_fees_sat: 144, @@ -145,59 +152,56 @@ pub(crate) fn new_chain_swap( id: generate_random_string(4), direction, claim_address: None, - lockup_address: "tb1p7cftn5u3ndt8ln0m6hruwyhsz8kc5sxt557ua03qcew0z29u5paqh8f7uu".to_string(), - timeout_block_height: 2868778, - preimage: "bbce422d96c0386c3a6c1b1fe11fc7be3fdd871c6855db6ab2e319e96ec19c78".to_string(), + lockup_address: "bc1ppavr8a6y2mnpgkv9sppphht4n6w4t6l9h8vm9lka0ej2tm0u5hjse2hkwg".to_string(), + timeout_block_height: 879692, + preimage: "7b9c67470e18c2cb6eadb8c78a9d4e64905ad780245d5a7015c55d378fdea782".to_string(), description: Some("Bitcoin transfer".to_string()), create_response_json: r#"{ "claim_details": { "swapTree": { "claimLeaf": { - "output": "82012088a914e5ec6c5b814b2d8616c1a0da0acc8b3388cf80d78820e5f32fc89e6947ca08a7855a99ac145f7de599446a0cc0ff4c9aa2694baa1138ac", + "output": "82012088a91459df772842f62a273d91a5c91824862981ca857788207aa38615d0b9b74c103ad38c1d796f61dadc499f600b900c3d6904ebefbad43fac", "version": 196 }, "refundLeaf": { - "output": "20692bbff63e48c1c05c5efeb7080f7c2416d2f9ecb79d217410eabc125f4d2ff0ad0312a716b1", + "output": "203caa9c0b9247e013b602693fa85c24be731e93e088d4b563ee54f44a8ad5a76cad03c3ff30b1", "version": 196 } }, - "lockupAddress": "tlq1pq0gfse32q454tmr30t7yl6lx2sv5sswdzh3j0zygz9v5jwwdq6deaec8ntnjq55yrx300u9ts5ykqnfcpuzrypmtda9yuszq0zpl6j8l9tunvqjyrdm3", - "serverPublicKey": "02692bbff63e48c1c05c5efeb7080f7c2416d2f9ecb79d217410eabc125f4d2ff0", - "timeoutBlockHeight": 1484562, - "amount": 0, - "blindingKey": "ebdd91bb06b2282e879256ff1c1a976016a582fea5418188799b1598281b0a5b", - "refundAddress": null, - "claimAddress": null, - "bip21": null + "lockupAddress": "lq1pqw8ct25kd47dejyesyvk3g2kaf8s9uhq4se7r2kj9y9hhvu9ug5thxlpn9y63s78kc2mcp6nujavckvr42q7hwkhqq9hfz46nth22hfp3em0ulm4nsuf", + "serverPublicKey": "023caa9c0b9247e013b602693fa85c24be731e93e088d4b563ee54f44a8ad5a76c", + "timeoutBlockHeight": 3211203, + "amount": 24720, + "blindingKey": "95375b0c8a01e6e60520b497086dbb9986b36ebb42da5bed64386620f92785b6" }, "lockup_details": { "swapTree": { "claimLeaf": { - "output": "82012088a914e5ec6c5b814b2d8616c1a0da0acc8b3388cf80d7882039688adbf0625672ec56e713e65ce809ee84e96525a13a68fe521588bf41628cac", + "output": "82012088a91459df772842f62a273d91a5c91824862981ca85778820d7bafe69414d072b5cbe46b4c397327f26478c7e515e64fdb0773aa1f63d1261ac", "version": 192 }, "refundLeaf": { - "output": "20edf1db3da18ad19962c8dfd7566048c7dc2e11f3d6580cbfed8f9a1321ffe4c7ad032ac62bb1", + "output": "20ee43447357096151fb251fc7b831eab2b64f330dfa2c8b3dc89721baba9715faad034c6c0db1", "version": 192 } }, - "lockupAddress": "tb1p7cftn5u3ndt8ln0m6hruwyhsz8kc5sxt557ua03qcew0z29u5paqh8f7uu", - "serverPublicKey": "0239688adbf0625672ec56e713e65ce809ee84e96525a13a68fe521588bf41628c", - "timeoutBlockHeight": 2868778, - "amount": 18360, - "blindingKey": null, - "refundAddress": null, - "claimAddress": null, - "bip21": "bitcoin:tb1p7cftn5u3ndt8ln0m6hruwyhsz8kc5sxt557ua03qcew0z29u5paqh8f7uu?amount=0.0001836&label=Send%20to%20L-BTC%20address" + "lockupAddress": "bc1ppavr8a6y2mnpgkv9sppphht4n6w4t6l9h8vm9lka0ej2tm0u5hjse2hkwg", + "serverPublicKey": "02d7bafe69414d072b5cbe46b4c397327f26478c7e515e64fdb0773aa1f63d1261", + "timeoutBlockHeight": 879692, + "amount": 25678, + "bip21": "bitcoin:bc1ppavr8a6y2mnpgkv9sppphht4n6w4t6l9h8vm9lka0ej2tm0u5hjse2hkwg?amount=0.00025678&label=Send%20to%20L-BTC%20address" } }"#.to_string(), - claim_private_key: "4b04c3b95570fc48c7f33bc900b801245c2be31b90d41616477574aedc5b9d28".to_string(), - refund_private_key: "9e23d322577cfeb2b5490f3f86db58c806004afcb7c88995927bfdfc1c64cd8c".to_string(), - payer_amount_sat: 18360, - actual_payer_amount_sat: None, - receiver_amount_sat: 17592, + claim_private_key: "1ba6e26809dee750a4da5dc11a289be6aec1119ed1f3e4e36767af4a5f502924".to_string(), + refund_private_key: "d13cbec86beb2be0ddca407d844061533cb0b621585221b8bf32b04872a0fc5e".to_string(), + payer_amount_sat: 25678, + actual_payer_amount_sat: match set_actual_payer_amount { + true => {Some(25678)} + false => {None} + }, + receiver_amount_sat: 24706, accepted_receiver_amount_sat: None, - claim_fees_sat: 144, + claim_fees_sat: 14, server_lockup_tx_id: None, user_lockup_tx_id, claim_tx_id: None, @@ -206,83 +210,80 @@ pub(crate) fn new_chain_swap( state: payment_state.unwrap_or(PaymentState::Created), accept_zero_conf, pair_fees_json: r#"{ - "hash": "43087e267db95668b9b7c48efcf44d922484870f1bdb8b926e5d6b76bf4d0709", - "rate": 1, - "limits": { - "maximal": 4294967, - "minimal": 10000, - "maximalZeroConf": 0 - }, - "fees": { - "percentage": 0.25, - "minerFees": { - "server": 4455, - "user": { - "claim": 3108, - "lockup": 276 - } - } + "hash": "5e82a62a8c884d18989519ea0bab759504dbd240babb52d63ba69b30844bd558", + "rate": 1, + "limits": { + "maximal": 25000000, + "minimal": 25000, + "maximalZeroConf": 0 + }, + "fees": { + "percentage": 0.1, + "minerFees": { + "server": 932, + "user": { + "lockup": 924, + "claim": 14 + } } + } }"#.to_string(), version: 0, }, Direction::Outgoing => ChainSwap { id: generate_random_string(4), direction, - claim_address: Some("14DeLtifrayJXAWft3qhPbdY4HVJUgMyx1".to_string()), - lockup_address: "tlq1pqg4e5r5a59gdl26ud6s7gna3mchqs20ycwl2lp67ejzy69fl7dwccwx9nqtr6ef848k7vpmvmdhsyeq2wp3vtn3gnlenhd0wrasv4qvr2dk0nz5tu0rw".to_string(), - timeout_block_height: 1481523, - preimage: "a95a028483df6112c15fdef513d9d8255ff0951d5c0856f85cf9c98352a0f71a".to_string(), + claim_address: Some("bc1qpvht5g3z9285z28emd0wa78r42jrrftdtkm0q0".to_string()), + lockup_address: "lq1pqv5jgrzshx2fxtfj2heael9lp0a7pkzk09v30yzgk9z8x6xjyw0hdk25fvxzs6rmhck3sty3ttn79mzrx5dp05wtfercqn29z4zquu6kp85kaqs620am".to_string(), + timeout_block_height: 3211648, + preimage: "22e45767b017782b4d9e72dcc22031b01059b3d76338132ac877f87877930230".to_string(), description: Some("Bitcoin transfer".to_string()), create_response_json: r#"{ - "claim_details": { - "swapTree":{ - "claimLeaf": { - "output": "82012088a9146a01e0a34b4e581da5133b5113b54b9033bb93dc8820dcfe4c6b840656e9e9cd53ba8b917d27a8091cba93b115b38e38d006d8a64e07ac", - "version": 192 - }, - "refundLeaf":{ - "output": "20265c09aff38287656da668bf69f9a4372fe7f4c788afef3e481c3bf99d7da54cad034ac42bb1", - "version": 192 - } - }, - "lockupAddress": "tb1pujr9d8sqwvhjq8z9fdpp25jjm8t0934qyg22mj36g06cx8g5r8cst6eq8p", - "serverPublicKey": "03265c09aff38287656da668bf69f9a4372fe7f4c788afef3e481c3bf99d7da54c", - "timeoutBlockHeight": 2868298, - "amount": 0, - "blindingKey": null, - "refundAddress": null, - "claimAddress": null, - "bip21": null + "claim_details": { + "swapTree": { + "claimLeaf": { + "output": "82012088a91494d7b35ffb1ddf8fede14bf4ae206d01a12ab86688204a992db2a3d9f8efc07c9189b9fbc1627888d9a7aafbb472a2d69923d5e82d4fac", + "version": 192 + }, + "refundLeaf": { + "output": "201667d71fcb9c4c68ef5cbe6776efa86489ea8dc42e14ae3023d6c13c67a8971fad03346c0db1", + "version": 192 + } }, - "lockup_details": { - "swapTree": { - "claimLeaf": { - "output": "82012088a9146a01e0a34b4e581da5133b5113b54b9033bb93dc88202f40110df011392abfbf3efefe179dea85b9f4b499a2a808b68d03b61f1d9a62ac", - "version": 196 - }, - "refundLeaf": { - "output": "2051d819f25d113c42c047545facf55aa2b15af9056af020a3d7cd61ebc2adbecfad03339b16b1", - "version": 196 - } - }, - "lockupAddress": "tlq1pqg4e5r5a59gdl26ud6s7gna3mchqs20ycwl2lp67ejzy69fl7dwccwx9nqtr6ef848k7vpmvmdhsyeq2wp3vtn3gnlenhd0wrasv4qvr2dk0nz5tu0rw", - "serverPublicKey": "022f40110df011392abfbf3efefe179dea85b9f4b499a2a808b68d03b61f1d9a62", - "timeoutBlockHeight": 1481523, - "amount": 0, - "blindingKey": "f69c69bec80dc0161f6c03367a269ce9780f04a8702916d17a13009552251f44", - "refundAddress": null, - "claimAddress": null, - "bip21": "liquidtestnet:tlq1pqg4e5r5a59gdl26ud6s7gna3mchqs20ycwl2lp67ejzy69fl7dwccwx9nqtr6ef848k7vpmvmdhsyeq2wp3vtn3gnlenhd0wrasv4qvr2dk0nz5tu0rw?amount=0.00025247&label=Send%20to%20BTC%20address&assetid=144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49" - } - }"#.to_string(), - claim_private_key: "7d3cbecfb76cb8eccc2c2131f3e744311d3655377fe8723d23acb55b041b2b16".to_string(), - refund_private_key: "2644c60cc6cd454ea809f0e32fc2871ab7c26603e3009e1fd313ae886c137eaa".to_string(), - payer_amount_sat: 25490, - actual_payer_amount_sat: None, - receiver_amount_sat: 20000, + "lockupAddress": "bc1p09ze8yqeydejw44a7ah9u0c74wvzz86h75e9tvvqm7kv0hcgp5ystgkcrs", + "serverPublicKey": "031667d71fcb9c4c68ef5cbe6776efa86489ea8dc42e14ae3023d6c13c67a8971f", + "timeoutBlockHeight": 879668, + "amount": 100555 + }, + "lockup_details": { + "swapTree": { + "claimLeaf": { + "output": "82012088a91494d7b35ffb1ddf8fede14bf4ae206d01a12ab86688204f96e342af0c7f45c84e4156eee1b6572c3c4f2b0be47160589df5f4cd700a78ac", + "version": 196 + }, + "refundLeaf": { + "output": "20e1b6fd943788d6fc1bc0f23d99c0f18f935b668801ec4f92d085bfa039a124a3ad03800131b1", + "version": 196 + } + }, + "lockupAddress": "lq1pqv5jgrzshx2fxtfj2heael9lp0a7pkzk09v30yzgk9z8x6xjyw0hdk25fvxzs6rmhck3sty3ttn79mzrx5dp05wtfercqn29z4zquu6kp85kaqs620am", + "serverPublicKey": "024f96e342af0c7f45c84e4156eee1b6572c3c4f2b0be47160589df5f4cd700a78", + "timeoutBlockHeight": 3211648, + "amount": 101441, + "blindingKey": "9701269bbf4f31bfe67e816db018ea9efcfd5b8cd3123664a0fcf1fc6c321cc5", + "bip21": "liquidnetwork:lq1pqv5jgrzshx2fxtfj2heael9lp0a7pkzk09v30yzgk9z8x6xjyw0hdk25fvxzs6rmhck3sty3ttn79mzrx5dp05wtfercqn29z4zquu6kp85kaqs620am?amount=0.00101441&label=Send%20to%20BTC%20address&assetid=6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d" + } + }"#.to_string(), + claim_private_key: "5241c2075ddf54e02ec2415452e3d29c62a85ac0de139f5638fbb382dab742f6".to_string(), + refund_private_key: "6f7f8b1b613adc7dd3c946ac71b5e823f9c4bba3cca5d11d8e365be24469dbda".to_string(), + payer_amount_sat: 101469, + actual_payer_amount_sat: match set_actual_payer_amount { + true => {Some(101469)} + false => {None} + }, + receiver_amount_sat: 100000, accepted_receiver_amount_sat: None, - claim_fees_sat: 2109, + claim_fees_sat: 555, server_lockup_tx_id: None, user_lockup_tx_id, claim_tx_id: None, @@ -291,22 +292,23 @@ pub(crate) fn new_chain_swap( state: payment_state.unwrap_or(PaymentState::Created), accept_zero_conf, pair_fees_json: r#"{ - "hash": "3ec520412cee74863f2c75a9cd7b8d2077f68267632344ec3c4646e100883091", - "rate": 1, - "limits": { - "maximal": 4294967, - "minimal": 10000, - "maximalZeroConf": 0 - }, - "fees": { - "percentage": 0.25, - "minerFees": { - "server": 3384, - "user": { - "claim": 143, - "lockup": 4312 - } + "hash": "a273415feb1374219a2ec73a3d54a5b801f52cb8148fb9d159b086b7a766ddd3", + "rate": 1, + "limits": { + "maximal": 25000000, + "minimal": 25000, + "maximalZeroConf": 250000 + }, + "fees": { + "percentage": 0.1, + "minerFees": { + "server": 784, + "user": { + "lockup": 26, + "claim": 555 + } } + } }"#.to_string(), version: 0 }