Hello,
I’m trying to implement the logic, where bitcoin are sent from multiple wallet/address to one or more receivers.
Is it possible to do such thing?
I tried it implementing. Here is my code so far
fn transfer(account0: Account,
account1: Account,
address0: Address,
address1: Address,
utxos0: Vec<Utxo>,
utxos1: Vec<Utxo>,
amount0: u64,
amount1: u64,
fee: u64,
paid_by_sender: bool,
receiver: Address,
){
const DUST_THRESHOLD: u64 = 1_000;
let mut input = Vec::with_capacity(utxos0.len() + utxos1.len());
let mut index_of_utxos_of_addr0 = vec![];
let mut index_of_utxos_of_addr1 = vec![];
let (mut total_spent0, mut total_spent1) = (0, 0);
utxos0.iter().for_each(|utxo| {
let txin = TxIn {
sequence: Sequence::max_value(),
script_sig: ScriptBuf::new(),
witness: Witness::new(),
previous_output: OutPoint {
txid: Txid::from_raw_hash(
Hash::from_slice(&utxo.outpoint.txid).expect("should return hash"),
),
vout: utxo.outpoint.vout,
},
};
total_spent0 += utxo.value;
let current_len = input.len();
input.insert(current_len, txin);
index_of_utxos_of_addr0.push(current_len);
});
utxos1.iter().for_each(|utxo| {
let txin = TxIn {
sequence: Sequence::max_value(),
script_sig: ScriptBuf::new(),
witness: Witness::new(),
previous_output: OutPoint {
txid: Txid::from_raw_hash(
Hash::from_slice(&utxo.outpoint.txid).expect("should return hash"),
),
vout: utxo.outpoint.vout,
},
};
total_spent1 += utxo.value;
let current_len = input.len();
input.insert(current_len, txin);
index_of_utxos_of_addr1.push(current_len);
});
let mut output = vec![TxOut {
script_pubkey: receiver.script_pubkey(),
value: if *paid_by_sender {
amount0 + amount1
} else {
amount0 + amount1 - fee
},
}];
// block responsible for calculating and adding remaining account
{
let (fee0, fee1) = {
let is_even = fee % 2 == 0;
if is_even {
let fee_in_half = fee / 2;
(fee_in_half, fee_in_half)
} else {
let fee_in_half = (fee - 1) / 2;
(fee_in_half, fee_in_half + 1)
}
};
let (amount0, amount1) = if *paid_by_sender {
(amount0 + fee0, amount1 + fee1)
} else {
(*amount0, *amount1)
};
let remaining0 = total_spent0 - amount0;
if remaining0 > DUST_THRESHOLD {
output.push(TxOut {
script_pubkey: address0.script_pubkey(),
value: remaining0,
});
}
let remaining1 = total_spent1 - amount1;
if remaining1 > DUST_THRESHOLD {
output.push(TxOut {
script_pubkey: address1.script_pubkey(),
value: remaining1,
})
}
}
let mut txn = Transaction {
input,
output,
lock_time: LockTime::ZERO,
version: 2,
};
// signing the transaction
let (path0, pubkey0, path1, pubkey1) = read_config(|config| {
let ecdsa_key = config.ecdsa_public_key();
let path0 = account_to_derivation_path(account0);
let path1 = account_to_derivation_path(account1);
let pubkey0 = derive_public_key(&ecdsa_key, &path0).public_key;
let pubkey1 = derive_public_key(&ecdsa_key, &path1).public_key;
(
DerivationPath::new(path0),
pubkey0,
DerivationPath::new(path1),
pubkey1,
)
});
let txn_cache = SighashCache::new(txn.clone());
for (i, input) in txn.input.iter_mut().enumerate() {
if index_of_utxos_of_addr0.contains(&i) {
let sighash = txn_cache
.legacy_signature_hash(
i,
&address0.script_pubkey(),
EcdsaSighashType::All.to_u32(),
)
.unwrap();
let signature = ecdsa_sign(
sighash.as_byte_array().to_vec(),
path0.clone().into_inner(),
)
.await
.signature;
let mut signature = sec1_to_der(signature);
signature.push(EcdsaSighashType::All.to_u32() as u8);
let signature = PushBytesBuf::try_from(signature).unwrap();
let pubkey = PushBytesBuf::try_from(pubkey0.clone()).unwrap();
input.script_sig = Builder::new()
.push_slice(signature)
.push_slice(pubkey)
.into_script();
input.witness.clear();
} else {
let sighash = txn_cache
.legacy_signature_hash(
i,
&address1.script_pubkey(),
EcdsaSighashType::All.to_u32(),
)
.unwrap();
let signature = ecdsa_sign(
sighash.as_byte_array().to_vec(),
path1.clone().into_inner(),
)
.await
.signature;
let mut signature = sec1_to_der(signature);
signature.push(EcdsaSighashType::All.to_u32() as u8);
let signature = PushBytesBuf::try_from(signature).unwrap();
let pubkey = PushBytesBuf::try_from(pubkey1.clone()).unwrap();
input.script_sig = Builder::new()
.push_slice(signature)
.push_slice(pubkey)
.into_script();
input.witness.clear();
}
}
let txid = txn.txid().to_string();
let txn_bytes = bitcoin::consensus::serialize(&txn);
ic_cdk::println!("{}", hex::encode(&txn_bytes));
bitcoin_send_transaction(SendTransactionRequest {
network: read_config(|config| config.bitcoin_network()),
transaction: txn_bytes,
})
.await
.expect("failed to submit transaction");
}
running the command testmempoolaccept
returns:
docker compose exec bitcoind bitcoin-cli testmempoolaccept '["0200000002110a7b3d523d844b6df897abd6ceeeb4a58e3a23a6c4ab21ffb536b12206d32c000000006b483045022100
84cd95cb3f476a41d9a3105d5de09c58d1f5e7ae0ddeca30bcee2b4eeb901030022047a4b885496f58145cc3e6d7f7913628df3f242416b779a8a0873a500c3c8d4301210245920894fbc6be26ae3846
bba49c500f173d9808003885b824d897562a1dcb04ffffffff562fdea94d471d0dd24a50e8115d42124f217032b655950e7e41f20ef4cb95db000000006a4730440220048ad77430fbac066b7acb2c12
8eccba3814495e100cdf4584f62c6cf4cfd3f3022025a0d3898123ff646cf0afbac8ff17ce00ddad2d067d07cc410ddc69438b43ab012102f9a399aff43b35f2c6a54b94c5b4713d4147afc7321e3706
68869541dd9d37f4ffffffff0380969800000000001976a9144bb8e5433188d3469cdb8f58b20d8b9aac996e4488ac26acb694000000001976a9144d9f4cfd3c937800e6ee1d1175f9c79ec498ac7788
ac26acb694000000001976a914bd22b275bdb35f16b8e176f469400931cda803ec88ac00000000"]'
[
{
"txid": "5cf49ef507c06641fb44116845a3f378a06eedcc0fd3e0d0843000bcad5875ea",
"wtxid": "5cf49ef507c06641fb44116845a3f378a06eedcc0fd3e0d0843000bcad5875ea",
"allowed": false,
"reject-reason": "txn-already-in-mempool"
}
]