pub mod evm;
pub mod p;
pub mod x;
use std::{
fmt,
io::{self, Error, ErrorKind},
time::SystemTime,
};
use crate::{info as api_info, p as api_p, x as api_x};
use avalanche_types::{
avax,
ids::{self, node, short},
key::{self, keychain},
platformvm, secp256k1fx,
};
use ethers::prelude::*;
use ethers_providers::{Middleware, Provider};
#[derive(Debug, Clone)]
pub struct Wallet<T: key::ReadOnly + key::SignOnly> {
pub http_rpc: String,
pub network_id: u32,
pub network_name: String,
pub keychain: keychain::Keychain<T>,
pub ethers_signing_key: ethers_core::k256::ecdsa::SigningKey,
pub local_wallet: LocalWallet,
pub evm_provider: Provider<Http>,
pub h160_address: H160,
pub x_address: String,
pub p_address: String,
pub c_address: String,
pub short_address: short::Id,
pub eth_address: String,
pub x_chain_id: ids::Id,
pub p_chain_id: ids::Id,
pub c_chain_id: ids::Id,
pub c_chain_id_u256: U256,
pub tx_fee: u64,
pub avax_asset_id: ids::Id,
}
impl<T> fmt::Display for Wallet<T>
where
T: key::ReadOnly + key::SignOnly + Clone,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "http_rpc: {}\n", self.http_rpc)?;
write!(f, "network_id: {}\n", self.network_id)?;
write!(f, "network_name: {}\n", self.network_name)?;
write!(f, "h160_address: {}\n", self.h160_address)?;
write!(f, "x_address: {}\n", self.x_address)?;
write!(f, "p_address: {}\n", self.p_address)?;
write!(f, "c_address: {}\n", self.c_address)?;
write!(f, "short_address: {}\n", self.short_address)?;
write!(f, "eth_address: {}\n", self.eth_address)?;
write!(f, "x_chain_id: {}\n", self.x_chain_id)?;
write!(f, "p_chain_id: {}\n", self.p_chain_id)?;
write!(f, "c_chain_id: {}\n", self.c_chain_id)?;
write!(f, "c_chain_id_u256: {}\n", self.c_chain_id_u256)?;
write!(f, "tx_fee: {}\n", self.tx_fee)?;
write!(f, "avax_asset_id: {}", self.avax_asset_id)
}
}
impl<T> Wallet<T>
where
T: key::ReadOnly + key::SignOnly + Clone,
{
pub async fn new(http_rpc: &str, key: &T) -> io::Result<Self> {
let resp = api_info::get_network_id(http_rpc).await?;
let network_id = resp
.result
.expect("unexpected None GetNetworkIdResponse")
.network_id;
let resp = api_info::get_network_name(http_rpc).await?;
let network_name = resp
.result
.expect("unexpected None GetNetworkNameResponse")
.network_name;
let evm_provider = Provider::<Http>::try_from(http_rpc.to_string() + "/ext/bc/C/rpc")
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to create c_ '{}'", e)))?;
let c_chain_id_u256 = evm_provider
.get_chainid()
.await
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to get chainId '{}'", e)))?;
let keychain = keychain::Keychain::new(vec![key.clone()]);
let ethers_signing_key = keychain.keys[0].ethers_signing_key()?;
let local_wallet: LocalWallet = ethers_signing_key.clone().into();
let h160_address = keychain.keys[0].get_h160_address();
let resp = api_info::get_blockchain_id(http_rpc, "X").await?;
let x_chain_id = resp
.result
.expect("unexpected None GetBlockchainIdResponse")
.blockchain_id;
let resp = api_info::get_blockchain_id(http_rpc, "P").await?;
let p_chain_id = resp
.result
.expect("unexpected None GetBlockchainIdResponse")
.blockchain_id;
let resp = api_info::get_blockchain_id(http_rpc, "C").await?;
let c_chain_id = resp
.result
.expect("unexpected None GetBlockchainIdResponse")
.blockchain_id;
let resp = api_x::get_asset_description(http_rpc, "AVAX").await?;
let resp = resp
.result
.expect("unexpected None GetAssetDescriptionResult");
let avax_asset_id = resp.asset_id;
let resp = api_info::get_tx_fee(http_rpc).await?;
let tx_fee = resp.result.unwrap().tx_fee;
let w = Self {
http_rpc: http_rpc.to_string(),
network_id,
network_name,
keychain,
ethers_signing_key,
local_wallet,
evm_provider,
h160_address,
x_address: key.get_address("X", network_id).unwrap(),
p_address: key.get_address("P", network_id).unwrap(),
c_address: key.get_address("C", network_id).unwrap(),
short_address: key.get_short_address(),
eth_address: key.get_eth_address(),
x_chain_id,
p_chain_id,
c_chain_id,
c_chain_id_u256,
tx_fee,
avax_asset_id,
};
log::info!("initiated the wallet\n{}", w);
Ok(w)
}
pub async fn get_utxos_x(&self) -> io::Result<Vec<avax::Utxo>> {
let resp = api_x::get_utxos(&self.http_rpc, &self.p_address).await?;
let utxos = resp
.result
.expect("unexpected None GetUtxosResult")
.utxos
.expect("unexpected None Utxos");
Ok(utxos)
}
pub async fn get_balance_x(&self) -> io::Result<u64> {
let resp = api_x::get_balance(&self.http_rpc, &self.x_address).await?;
let cur_balance = resp
.result
.expect("unexpected None GetBalanceResult")
.balance;
Ok(cur_balance)
}
pub async fn get_utxos_p(&self) -> io::Result<Vec<avax::Utxo>> {
let resp = api_p::get_utxos(&self.http_rpc, &self.p_address).await?;
let utxos = resp
.result
.expect("unexpected None GetUtxosResult")
.utxos
.expect("unexpected None Utxos");
Ok(utxos)
}
pub async fn get_balance_p(&self) -> io::Result<u64> {
let resp = api_p::get_balance(&self.http_rpc, &self.p_address).await?;
let cur_balance = resp
.result
.expect("unexpected None GetBalanceResult")
.balance
.expect("unexpected None balance");
Ok(cur_balance)
}
pub async fn get_balance_c_u256(&self) -> io::Result<U256> {
self.evm_provider
.get_balance(self.h160_address, None)
.await
.map_err(|e| Error::new(ErrorKind::Other, format!("failed to get_balance '{}'", e)))
}
async fn is_validator(&self, node_id: &node::Id) -> io::Result<bool> {
let resp = api_p::get_current_validators(&self.http_rpc).await?;
let resp = resp
.result
.expect("unexpected None GetCurrentValidatorResult");
let validators = resp.validators.expect("unexpected None vaidators");
for validator in validators.iter() {
let val_id = validator.node_id.unwrap();
if val_id.eq(node_id) {
return Ok(true);
}
log::info!("current validator: {}", node_id);
}
Ok(false)
}
async fn stake(
&self,
stake_amount: u64,
fee: u64,
) -> io::Result<(
Vec<avax::TransferableInput>,
Vec<avax::TransferableOutput>,
Vec<avax::TransferableOutput>,
Vec<Vec<T>>,
)> {
let utxos = self.get_utxos_p().await?;
let now_unix = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("unexpected None duration_since")
.as_secs();
let mut transferable_inputs: Vec<avax::TransferableInput> = Vec::new();
let mut unlocked_outputs: Vec<avax::TransferableOutput> = Vec::new();
let mut locked_outputs: Vec<avax::TransferableOutput> = Vec::new();
let mut signers: Vec<Vec<T>> = Vec::new();
let mut amount_staked: u64 = 0_u64;
for utxo in utxos.iter() {
if amount_staked >= stake_amount {
break;
}
if utxo.asset_id != self.avax_asset_id {
continue;
}
if utxo.stakeable_lock_out.is_none() {
continue;
}
let stakeable_lock_out = utxo.stakeable_lock_out.clone().unwrap();
if stakeable_lock_out.locktime <= now_unix {
continue;
}
let transfer_output = stakeable_lock_out.clone().transfer_output;
let res = self.keychain.spend(&transfer_output, now_unix);
if res.is_none() {
continue;
}
let (transfer_input, input_signers) = res.unwrap();
let mut remaining_value = transfer_input.amount;
let amount_to_stake = (stake_amount - amount_staked) .min(
remaining_value, );
amount_staked += amount_to_stake;
remaining_value -= amount_to_stake;
transferable_inputs.push(avax::TransferableInput {
utxo_id: utxo.utxo_id.clone(),
asset_id: utxo.asset_id,
stakeable_lock_in: Some(platformvm::StakeableLockIn {
locktime: stakeable_lock_out.locktime,
transfer_input,
}),
..avax::TransferableInput::default()
});
unlocked_outputs.push(avax::TransferableOutput {
asset_id: utxo.asset_id,
stakeable_lock_out: Some(platformvm::StakeableLockOut {
locktime: stakeable_lock_out.clone().locktime,
transfer_output: secp256k1fx::TransferOutput {
amount: amount_to_stake,
output_owners: stakeable_lock_out.clone().transfer_output.output_owners,
},
}),
..avax::TransferableOutput::default()
});
if remaining_value > 0 {
locked_outputs.push(avax::TransferableOutput {
asset_id: utxo.asset_id,
stakeable_lock_out: Some(platformvm::StakeableLockOut {
locktime: stakeable_lock_out.clone().locktime,
transfer_output: secp256k1fx::TransferOutput {
amount: remaining_value,
output_owners: stakeable_lock_out.clone().transfer_output.output_owners,
},
}),
..avax::TransferableOutput::default()
});
}
signers.push(input_signers);
}
let mut amount_burned = 0_u64;
for utxo in utxos.iter() {
if amount_staked >= stake_amount && amount_burned >= fee {
break;
}
if utxo.asset_id != self.avax_asset_id {
continue;
}
let (skip, transfer_output) = {
if utxo.transfer_output.is_some() {
let transfer_output = utxo.transfer_output.clone().unwrap();
(false, transfer_output)
} else {
let stakeable_lock_out = utxo.stakeable_lock_out.clone().unwrap();
(
stakeable_lock_out.locktime > now_unix,
stakeable_lock_out.transfer_output,
)
}
};
if skip {
continue;
}
let res = self.keychain.spend(&transfer_output, now_unix);
if res.is_none() {
continue;
}
let (transfer_input, input_signers) = res.unwrap();
let mut remaining_value = transfer_input.amount;
let amount_to_burn = (fee - amount_burned) .min(
remaining_value, );
amount_burned += amount_to_burn;
remaining_value -= amount_to_burn;
let amount_to_stake = (stake_amount - amount_staked) .min(
remaining_value, );
amount_staked += amount_to_stake;
remaining_value -= amount_to_stake;
transferable_inputs.push(avax::TransferableInput {
utxo_id: utxo.utxo_id.clone(),
asset_id: utxo.asset_id,
transfer_input: Some(transfer_input),
..avax::TransferableInput::default()
});
if amount_to_stake > 0 {
unlocked_outputs.push(avax::TransferableOutput {
asset_id: utxo.asset_id,
transfer_output: Some(secp256k1fx::TransferOutput {
amount: amount_to_stake,
output_owners: secp256k1fx::OutputOwners {
locktime: 0,
threshold: 1,
addrs: vec![self.short_address.clone()],
},
}),
..avax::TransferableOutput::default()
});
}
if remaining_value > 0 {
locked_outputs.push(avax::TransferableOutput {
asset_id: utxo.asset_id,
transfer_output: Some(secp256k1fx::TransferOutput {
amount: remaining_value,
output_owners: secp256k1fx::OutputOwners {
locktime: 0,
threshold: 1,
addrs: vec![self.short_address.clone()],
},
}),
..avax::TransferableOutput::default()
});
}
signers.push(input_signers);
}
if amount_staked > 0 && amount_staked < stake_amount {
return Err(Error::new(
ErrorKind::Other,
"insufficient balance for stake amount",
));
}
if amount_burned > 0 && amount_burned < fee {
return Err(Error::new(
ErrorKind::Other,
"insufficient balance for gas fee",
));
}
transferable_inputs.sort();
unlocked_outputs.sort();
locked_outputs.sort();
Ok((
transferable_inputs,
unlocked_outputs,
locked_outputs,
signers,
))
}
}