Skip to content

Instantly share code, notes, and snippets.

@al-f4lc0n
Created March 9, 2025 13:46
Show Gist options
  • Select an option

  • Save al-f4lc0n/bedd32697390069be3efac1c906096a4 to your computer and use it in GitHub Desktop.

Select an option

Save al-f4lc0n/bedd32697390069be3efac1c906096a4 to your computer and use it in GitHub Desktop.
use bitcoin::{ absolute, transaction::Version, Address, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut };
use bitcoin::consensus::encode::serialize_hex;
use bitcoincore_rpc::{json, Client as BitcoinClient, RpcApi as _};
use clarity::types::{chainstate::StacksAddress, Address as _};
use clarity::vm::types::{PrincipalData, StandardPrincipalData};
use emily_client::apis::configuration::{ApiKey, Configuration};
use emily_client::apis::deposit_api;
use emily_client::models::CreateDepositRequestBody;
use sbtc::deposits::{DepositScriptInputs, ReclaimScriptInputs};
use signer::config::Settings;
use signer::keys::PublicKey;
use signer::stacks::api::{StacksClient, StacksInteract};
use std::str::FromStr;
/* secret key:
spawn knee orchard patrol merge forget dust position daring short bridge elevator attitude leopard opera appear auction limit magic hover tunnel museum quantum manual
*/
const TEST_AUDITOR_STACKS_ADDR: &str = "ST2BEV097EV2R9ZMFRMRT904QB5RFYMA0683TC111";
const TEST_CONFIG_PATH: &str = "signer/src/config/default.toml";
const TEST_DEPLOYER_STACKS_ADDR: &str = "SN3R84XZYA63QS28932XQF3G1J8R9PC3W76P9CSQS";
const TEST_DEPOSIT_LOCK_TIME: u32 = 10;
const TEST_BTC_MINER_ADDRESS: &str = "miEJtNKa3ASpA19v5ZhvbKTEieYjLpzCYT";
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("┏ xxxx");
many_deposits_in_one_btc_tx(
100, // number_of_deposits
1000000, // per_deposit_amount
100000, // per_deposit_max_fee
100000, // btc_tx_fee
).await?;
println!("┗ end");
Ok(())
}
fn get_emily_client_config() -> Result<Configuration, Box<dyn std::error::Error>> {
let settings = Settings::new(Some(TEST_CONFIG_PATH))?;
let emily_api_endpoint = settings.emily.endpoints.first().unwrap().clone();
Ok(Configuration {
base_path: emily_api_endpoint.to_string().trim_end_matches("/").to_string(),
api_key: Some(ApiKey {
prefix: None,
key: emily_api_endpoint.username().to_string(),
}),
..Default::default()
})
}
fn get_stacks_client() -> Result<StacksClient, Box<dyn std::error::Error>> {
let settings = Settings::new(Some(TEST_CONFIG_PATH))?;
let url = settings
.stacks
.endpoints
.first()
.expect("No Stacks endpoints configured");
Ok(StacksClient::new(url.clone()).expect("Failed to create Stacks client"))
}
fn get_btc_client() -> Result<BitcoinClient, Box<dyn std::error::Error>> {
let settings = Settings::new(Some(TEST_CONFIG_PATH))?;
let bitcoin_url = format!(
"{}wallet/depositor",
settings
.bitcoin
.rpc_endpoints
.first()
.expect("No Bitcoin RPC endpoints configured")
);
let btc_client = BitcoinClient::new(
&bitcoin_url,
bitcoincore_rpc::Auth::UserPass("devnet".into(), "devnet".into()),
).expect("Failed to create Bitcoin RPC client");
Ok(btc_client)
}
async fn get_current_aggregate_key() -> Result<PublicKey, Box<dyn std::error::Error>> {
let stacks_client = get_stacks_client()?;
let deployer = PrincipalData::parse_standard_principal(&TEST_DEPLOYER_STACKS_ADDR)
.map(StacksAddress::from)?;
match stacks_client.get_current_signers_aggregate_key(&deployer).await? {
Some(aggregate_key) => Ok(aggregate_key),
_ => Err(Box::from("no aggregate key")),
}
}
async fn many_deposits_in_one_btc_tx(
number_of_deposits: u64,
per_deposit_amount: u64,
per_deposit_max_fee: u64,
btc_tx_fee: u64,
) -> Result<(), Box<dyn std::error::Error>> {
let aggregate_key = get_current_aggregate_key().await?;
let deposit_script = DepositScriptInputs {
signers_public_key: aggregate_key.into(),
max_fee: per_deposit_max_fee,
recipient: PrincipalData::Standard(StandardPrincipalData::from(
StacksAddress::from_string(TEST_AUDITOR_STACKS_ADDR).unwrap(),
)),
};
let reclaim_script = ReclaimScriptInputs::try_new(
TEST_DEPOSIT_LOCK_TIME,
ScriptBuf::from_bytes(vec![])
)?;
let btc_client = get_btc_client()?;
let total_spend = Amount::from_sat(number_of_deposits * per_deposit_amount + btc_tx_fee);
let opts = json::ListUnspentQueryOptions {
minimum_amount: Some(total_spend),
..Default::default()
};
let unspent = btc_client
.list_unspent(Some(1), None, None, None, Some(opts))?
.into_iter()
.next()
.unwrap();
let mut unsigned_tx = Transaction {
input: vec![TxIn {
previous_output: OutPoint {
txid: unspent.txid,
vout: unspent.vout,
},
script_sig: Default::default(),
sequence: Sequence::ZERO,
witness: Default::default(),
}],
output: vec![
TxOut {
value: unspent.amount - total_spend,
script_pubkey: unspent.script_pub_key,
},
],
version: Version::TWO,
lock_time: absolute::LockTime::ZERO,
};
for _ in 0..number_of_deposits {
unsigned_tx.output.push(TxOut {
value: Amount::from_sat(per_deposit_amount),
script_pubkey: sbtc::deposits::to_script_pubkey(
deposit_script.deposit_script(),
reclaim_script.reclaim_script(),
),
},);
}
let signed_tx = btc_client.sign_raw_transaction_with_wallet(&unsigned_tx, None, None)?;
let txid = btc_client.send_raw_transaction(&signed_tx.hex)?;
println!("┃ btc tx sent: {:?}", txid);
let emily_client_config = get_emily_client_config()?;
for i in 0..number_of_deposits {
deposit_api::create_deposit(
&emily_client_config,
CreateDepositRequestBody {
bitcoin_tx_output_index: i as u32 + 1,
bitcoin_txid: txid.to_string(),
deposit_script: deposit_script.deposit_script().to_hex_string(),
reclaim_script: reclaim_script.reclaim_script().to_hex_string(),
transaction_hex: serialize_hex(&unsigned_tx),
},
).await?;
println!("┃ deposit[{:?}] request created", i + 1);
}
Ok(())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment