use std::{
cmp,
io::{self, Error, ErrorKind},
time::{Duration, SystemTime},
};
use crate::x as api_x;
use avalanche_types::{
avax, avm, choices::status::Status, formatting, ids::short, key, secp256k1fx,
};
use tokio::time::sleep;
impl<T> crate::wallet::Wallet<T>
where
T: key::ReadOnly + key::SignOnly + Clone,
{
pub async fn transfer_x_avax(
&self,
http_rpc: Option<String>,
receiver_short_addr: short::Id,
amount_to_transfer: u64,
check_acceptance: bool,
) -> io::Result<()> {
let http_rpc_ep = if let Some(ep) = &http_rpc {
ep.to_string()
} else {
self.http_rpc.clone()
};
let sender_short_addr = self.keychain.keys[0].get_short_address();
log::info!(
"transferring {} from {} to {} via {}",
amount_to_transfer,
sender_short_addr,
receiver_short_addr,
http_rpc_ep
);
let sender_x_utxos = api_x::get_utxos(&http_rpc_ep, &self.x_address).await?;
let sender_x_utxos_result = sender_x_utxos.result.unwrap();
let sender_x_utxos = sender_x_utxos_result.utxos.unwrap();
log::debug!(
"fetched UTXOs for inputs: numFetched {:?}, endIndex {:?} and {} UTXOs",
sender_x_utxos_result.num_fetched,
sender_x_utxos_result.end_index,
sender_x_utxos.len()
);
let mut inputs: Vec<avax::TransferableInput> = Vec::new();
let mut outputs: Vec<avax::TransferableOutput> = vec![
avax::TransferableOutput {
asset_id: self.avax_asset_id.clone(),
transfer_output: Some(secp256k1fx::TransferOutput {
amount: amount_to_transfer,
output_owners: secp256k1fx::OutputOwners {
locktime: 0,
threshold: 1,
addrs: vec![receiver_short_addr],
},
}),
..Default::default()
},
];
let mut remaining_amount_to_burn = amount_to_transfer + self.tx_fee;
let now_unix = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("unexpected None duration_since")
.as_secs();
for utxo in sender_x_utxos.iter() {
if utxo.asset_id != self.avax_asset_id {
continue;
}
if remaining_amount_to_burn == 0 {
continue;
}
if let Some(out) = &utxo.transfer_output {
let (input, _) = self.keychain.spend(out, now_unix).unwrap();
inputs.push(avax::TransferableInput {
utxo_id: utxo.utxo_id.clone(),
asset_id: utxo.asset_id.clone(),
transfer_input: Some(input.clone()),
..Default::default()
});
let amount_to_burn = cmp::min(
remaining_amount_to_burn, out.amount, );
remaining_amount_to_burn -= amount_to_burn;
let remaining_amount = out.amount - amount_to_burn;
if remaining_amount > 0 {
outputs.push(avax::TransferableOutput {
asset_id: self.avax_asset_id.clone(),
transfer_output: Some(secp256k1fx::TransferOutput {
amount: remaining_amount,
output_owners: secp256k1fx::OutputOwners {
locktime: 0,
threshold: 1,
addrs: vec![sender_short_addr.clone()],
},
}),
..Default::default()
})
}
}
}
inputs.sort();
outputs.sort();
log::debug!(
"baseTx has {} inputs and {} outputs",
inputs.len(),
outputs.len()
);
let base_tx = avax::BaseTx {
network_id: self.network_id,
blockchain_id: self.x_chain_id.clone(),
transferable_outputs: Some(outputs),
transferable_inputs: Some(inputs.clone()),
..Default::default()
};
let mut signers: Vec<Vec<T>> = Vec::new();
for _ in 0..inputs.len() {
signers.push(vec![self.keychain.keys[0].clone()]);
}
if inputs.len() > 1 {
log::debug!("signing for multiple inputs ({} inputs)", inputs.len());
}
let mut tx = avm::tx::Tx::new(base_tx);
tx.sign(signers).unwrap();
let signed_bytes = tx.unsigned_tx.metadata.clone().unwrap().bytes;
let hex_tx = formatting::encode_hex_with_checksum(&signed_bytes);
let resp = api_x::issue_tx(&http_rpc_ep, &hex_tx).await?;
if resp.result.is_none() {
return Err(Error::new(
ErrorKind::Other,
format!("failed to issue tx {:?}", resp.error),
));
}
let tx_id = resp.result.unwrap().tx_id.to_string();
log::info!("{} successfully issued", tx_id);
if !check_acceptance {
log::debug!("skipping checking acceptance...");
return Ok(());
}
sleep(Duration::from_millis(700)).await;
loop {
let resp = api_x::get_tx_status(&http_rpc_ep, &tx_id).await?;
let status = resp.result.unwrap().status;
if status == Status::Accepted {
log::info!("{} successfully accepted", tx_id);
break;
}
log::warn!("{} {} (not accepted yet in {})", tx_id, status, http_rpc_ep);
sleep(Duration::from_secs(1)).await;
}
Ok(())
}
}