|
| 1 | +use std::{io::Cursor, time::Duration}; |
| 2 | + |
| 3 | +use bdk::{ |
| 4 | + bitcoin::{psbt::serialize::Serialize, PrivateKey}, |
| 5 | + blockchain::{ |
| 6 | + ConfigurableBlockchain, ElectrumBlockchain, ElectrumBlockchainConfig, |
| 7 | + }, |
| 8 | + database::MemoryDatabase, |
| 9 | + template::P2Wpkh, |
| 10 | + SyncOptions, Wallet, |
| 11 | +}; |
| 12 | +use blockstack_lib::{ |
| 13 | + codec::StacksMessageCodec, |
| 14 | + util::hash::hex_bytes, |
| 15 | + vm::{ |
| 16 | + types::{QualifiedContractIdentifier, StandardPrincipalData}, |
| 17 | + Value, |
| 18 | + }, |
| 19 | +}; |
| 20 | +use romeo::{config::Config, stacks_client::StacksClient}; |
| 21 | +use sbtc_cli::commands::{ |
| 22 | + broadcast::{broadcast_tx, BroadcastArgs}, |
| 23 | + deposit::{build_deposit_tx, DepositArgs}, |
| 24 | + withdraw::{build_withdrawal_tx, WithdrawalArgs}, |
| 25 | +}; |
| 26 | +use stacks_core::address::StacksAddress; |
| 27 | +use tokio::time::sleep; |
| 28 | +use url::Url; |
| 29 | + |
| 30 | +/// Wait until all your services are ready before running. |
| 31 | +/// Don't forget to fund W0 (deployer) and W1 (recipient). |
| 32 | +#[tokio::main] |
| 33 | +async fn main() { |
| 34 | + let mut config = |
| 35 | + Config::from_path("./devenv/sbtc/docker/config.json").unwrap(); |
| 36 | + config.stacks_node_url = "http://localhost:3999".parse().unwrap(); |
| 37 | + config.bitcoin_node_url = "http://localhost:18443".parse().unwrap(); |
| 38 | + config.electrum_node_url = "tcp://localhost:60401".parse().unwrap(); |
| 39 | + |
| 40 | + let blockchain = |
| 41 | + ElectrumBlockchain::from_config(&ElectrumBlockchainConfig { |
| 42 | + url: config.electrum_node_url.clone().into(), |
| 43 | + socks5: None, |
| 44 | + retry: 3, |
| 45 | + timeout: Some(10), |
| 46 | + stop_gap: 10, |
| 47 | + validate_domain: false, |
| 48 | + }) |
| 49 | + .unwrap(); |
| 50 | + |
| 51 | + let recipient_p2wpkh_wif = |
| 52 | + "cNcXK2r8bNdWJQymtAW8tGS7QHNtFFvG5CdXqhhT752u29WspXRM"; |
| 53 | + |
| 54 | + // W1 |
| 55 | + let wallet = { |
| 56 | + let private_key = PrivateKey::from_wif(recipient_p2wpkh_wif).unwrap(); |
| 57 | + |
| 58 | + Wallet::new( |
| 59 | + P2Wpkh(private_key), |
| 60 | + Some(P2Wpkh(private_key)), |
| 61 | + bdk::bitcoin::Network::Regtest, |
| 62 | + MemoryDatabase::default(), |
| 63 | + ) |
| 64 | + .unwrap() |
| 65 | + }; |
| 66 | + |
| 67 | + loop { |
| 68 | + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); |
| 69 | + let balance = wallet.get_balance().unwrap().confirmed; |
| 70 | + println!("recipient's btc: {balance}"); |
| 71 | + if balance != 0 { |
| 72 | + break; |
| 73 | + } |
| 74 | + sleep(Duration::from_secs(1)).await; |
| 75 | + } |
| 76 | + |
| 77 | + let recipient = "ST2ST2H80NP5C9SPR4ENJ1Z9CDM9PKAJVPYWPQZ50"; |
| 78 | + let amount = 1000; |
| 79 | + |
| 80 | + // deposit |
| 81 | + { |
| 82 | + let electrum_url = |
| 83 | + Url::parse(config.electrum_node_url.as_str()).unwrap(); |
| 84 | + let tx = { |
| 85 | + let args = DepositArgs { |
| 86 | + node_url: electrum_url.clone(), |
| 87 | + wif: recipient_p2wpkh_wif.into(), |
| 88 | + network: bdk::bitcoin::Network::Regtest, |
| 89 | + recipient:recipient.into(), |
| 90 | + amount, |
| 91 | + sbtc_wallet: "bcrt1pte5zmd7qzj4hdu45lh9mmdm0nwq3z35pwnxmzkwld6y0a8g83nnqhj6vc0".into(), |
| 92 | + }; |
| 93 | + |
| 94 | + build_deposit_tx(&args).unwrap() |
| 95 | + }; |
| 96 | + |
| 97 | + broadcast_tx(&BroadcastArgs { |
| 98 | + node_url: electrum_url, |
| 99 | + tx: hex::encode(tx.serialize()), |
| 100 | + }) |
| 101 | + .unwrap(); |
| 102 | + } |
| 103 | + |
| 104 | + let deployer = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM"; |
| 105 | + let deployer_address = StacksAddress::transmute_stacks_address(deployer); |
| 106 | + let recipient_address = StacksAddress::transmute_stacks_address(recipient); |
| 107 | + |
| 108 | + let stacks_client = |
| 109 | + StacksClient::new(config.clone(), reqwest::Client::new()); |
| 110 | + |
| 111 | + println!("Waiting on sBTC mint"); |
| 112 | + // request token balance from the asset contract. |
| 113 | + while { |
| 114 | + let res: serde_json::Value = stacks_client |
| 115 | + .call_read_only_fn( |
| 116 | + QualifiedContractIdentifier::new( |
| 117 | + StandardPrincipalData::from(deployer_address), |
| 118 | + config.contract_name.clone(), |
| 119 | + ), |
| 120 | + "get-balance", |
| 121 | + recipient_address.to_string().as_str(), |
| 122 | + vec![StandardPrincipalData::from(recipient_address).into()], |
| 123 | + ) |
| 124 | + .await |
| 125 | + .unwrap(); |
| 126 | + |
| 127 | + assert!(res["okay"].as_bool().unwrap()); |
| 128 | + let bytes = |
| 129 | + hex_bytes(res["result"].as_str().unwrap().trim_start_matches("0x")) |
| 130 | + .unwrap(); |
| 131 | + |
| 132 | + let mut cursor = Cursor::new(&bytes); |
| 133 | + Value::consensus_deserialize(&mut cursor) |
| 134 | + .unwrap() |
| 135 | + .expect_result_ok() |
| 136 | + .expect_u128() |
| 137 | + } < amount as u128 |
| 138 | + { |
| 139 | + sleep(Duration::from_secs(2)).await; |
| 140 | + } |
| 141 | + |
| 142 | + let fee = 2000; |
| 143 | + // withdraw |
| 144 | + let args = WithdrawalArgs { |
| 145 | + node_url: config.electrum_node_url.clone(), |
| 146 | + network: bdk::bitcoin::Network::Regtest, |
| 147 | + // p2wpkh |
| 148 | + wif: "cNcXK2r8bNdWJQymtAW8tGS7QHNtFFvG5CdXqhhT752u29WspXRM".into(), |
| 149 | + // Stacks |
| 150 | + drawee_wif: "cR9hENRFiuHzKpj9B3QCTBrt19c5ZCJKHJwYcqj5dfB6aKyf6ndm" |
| 151 | + .into(), |
| 152 | + payee_address: "bcrt1q3zl64vadtuh3vnsuhdgv6pm93n82ye8q6cr4ch".into(), |
| 153 | + amount, |
| 154 | + fulfillment_fee: fee, |
| 155 | + sbtc_wallet: |
| 156 | + "bcrt1pte5zmd7qzj4hdu45lh9mmdm0nwq3z35pwnxmzkwld6y0a8g83nnqhj6vc0" |
| 157 | + .into(), |
| 158 | + }; |
| 159 | + |
| 160 | + let tx = build_withdrawal_tx(&args).unwrap(); |
| 161 | + |
| 162 | + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); |
| 163 | + let balance = wallet.get_balance().unwrap().confirmed; |
| 164 | + |
| 165 | + broadcast_tx(&BroadcastArgs { |
| 166 | + node_url: config.electrum_node_url.clone(), |
| 167 | + tx: hex::encode(tx.serialize()), |
| 168 | + }) |
| 169 | + .unwrap(); |
| 170 | + |
| 171 | + println!("Waiting on fulfillment"); |
| 172 | + loop { |
| 173 | + wallet.sync(&blockchain, SyncOptions::default()).unwrap(); |
| 174 | + let current = wallet.get_balance().unwrap().confirmed; |
| 175 | + // will fail if tx_fees is not an upper bound for real fees. |
| 176 | + let tx_fees = 400; |
| 177 | + if balance - fee - tx_fees < current { |
| 178 | + break; |
| 179 | + } |
| 180 | + sleep(Duration::from_secs(2)).await; |
| 181 | + } |
| 182 | +} |
0 commit comments