pub mod p;
pub mod x;
#[cfg(feature = "evm_ethers")]
pub mod evm_ethers;
use std::{
fmt,
io::{self, Error, ErrorKind},
sync::{Arc, Mutex},
};
use crate::{info as api_info, x as api_x};
use avalanche_types::{
ids::{self, short},
key, units,
};
use ethers::prelude::*;
use ethers_providers::{Middleware, Provider};
#[derive(Debug, Clone)]
pub struct Builder<T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone> {
pub key: T,
pub http_rpcs: Vec<String>,
pub subnet_evm_blockchain_id: Option<String>,
}
impl<T> Builder<T>
where
T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
{
pub fn new(key: &T) -> Self {
Self {
http_rpcs: Vec::new(),
key: key.clone(),
subnet_evm_blockchain_id: None,
}
}
#[must_use]
pub fn http_rpc(mut self, http_rpc: String) -> Self {
if self.http_rpcs.is_empty() {
self.http_rpcs = vec![http_rpc];
} else {
self.http_rpcs.push(http_rpc);
}
self
}
#[must_use]
pub fn http_rpcs(mut self, http_rpcs: Vec<String>) -> Self {
self.http_rpcs = http_rpcs;
self
}
#[must_use]
pub fn subnet_evm_blockchain_id(mut self, id: String) -> Self {
self.subnet_evm_blockchain_id = Some(id);
self
}
pub async fn build(&self) -> io::Result<Wallet<T>> {
log::info!("building wallet with {} endpoints", self.http_rpcs.len());
let keychain = key::secp256k1::keychain::Keychain::new(vec![self.key.clone()]);
let ethers_signing_key = keychain.keys[0].ethers_signing_key()?;
let local_wallet: LocalWallet = ethers_signing_key.clone().into();
let resp = api_info::get_network_id(&self.http_rpcs[0]).await?;
let network_id = resp
.result
.expect("unexpected None GetNetworkIdResponse")
.network_id;
let resp = api_info::get_network_name(&self.http_rpcs[0]).await?;
let network_name = resp
.result
.expect("unexpected None GetNetworkNameResponse")
.network_name;
let c_url_path = String::from("/ext/bc/C/rpc");
let mut c_providers = Vec::new();
for ep in self.http_rpcs.iter() {
let c_provider =
Provider::<Http>::try_from(ep.clone() + c_url_path.as_str()).map_err(|e| {
Error::new(
ErrorKind::Other,
format!("failed to create provider '{}'", e),
)
})?;
c_providers.push(c_provider);
}
let c_chain_id_u256 = c_providers[0].get_chainid().await.map_err(|e| {
Error::new(
ErrorKind::Other,
format!("failed to get chainId for C-chain '{}'", e),
)
})?;
let (subnet_evm_url_path, subnet_evm_providers, subnet_evm_chain_id_u256) =
if let Some(sv) = &self.subnet_evm_blockchain_id {
let subnet_evm_url_path = format!("/ext/bc/{}/rpc", sv).to_string();
let mut subnet_evm_providers = Vec::new();
for ep in self.http_rpcs.iter() {
let subnet_evm_provider =
Provider::<Http>::try_from(ep.clone() + subnet_evm_url_path.as_str())
.map_err(|e| {
Error::new(
ErrorKind::Other,
format!("failed to create provider '{}'", e),
)
})?;
subnet_evm_providers.push(subnet_evm_provider);
}
let subnet_evm_chain_id_u256 =
subnet_evm_providers[0].get_chainid().await.map_err(|e| {
Error::new(
ErrorKind::Other,
format!("failed to get chainId for subnet-evm '{}'", e),
)
})?;
(
Some(subnet_evm_url_path),
Some(subnet_evm_providers),
Some(subnet_evm_chain_id_u256),
)
} else {
(None, None, None)
};
let resp = api_info::get_blockchain_id(&self.http_rpcs[0], "X").await?;
let x_chain_id = resp
.result
.expect("unexpected None GetBlockchainIdResponse")
.blockchain_id;
let resp = api_info::get_blockchain_id(&self.http_rpcs[0], "P").await?;
let p_chain_id = resp
.result
.expect("unexpected None GetBlockchainIdResponse")
.blockchain_id;
let resp = api_info::get_blockchain_id(&self.http_rpcs[0], "C").await?;
let c_chain_id = resp
.result
.expect("unexpected None GetBlockchainIdResponse")
.blockchain_id;
let resp = api_x::get_asset_description(&self.http_rpcs[0], "AVAX").await?;
let resp = resp
.result
.expect("unexpected None GetAssetDescriptionResult");
let avax_asset_id = resp.asset_id;
let resp = api_info::get_tx_fee(&self.http_rpcs[0]).await?;
let tx_fee = resp.result.unwrap().tx_fee;
let (create_subnet_tx_fee, create_blockchain_tx_fee) = if network_id == 1 {
(1 * units::AVAX, 1 * units::AVAX)
} else {
(100 * units::MILLI_AVAX, 100 * units::MILLI_AVAX)
};
let h160_address = keychain.keys[0].get_h160_address();
let w = Wallet {
keychain,
ethers_signing_key,
local_wallet,
http_rpcs: self.http_rpcs.clone(),
http_rpc_idx: Arc::new(Mutex::new(0)),
network_id,
network_name,
c_url_path,
subnet_evm_url_path,
c_providers,
subnet_evm_providers,
h160_address,
x_address: self.key.get_address(network_id, "X").unwrap(),
p_address: self.key.get_address(network_id, "P").unwrap(),
c_address: self.key.get_address(network_id, "C").unwrap(),
short_address: self.key.get_short_address().unwrap(),
eth_address: self.key.get_eth_address(),
x_chain_id,
p_chain_id,
c_chain_id,
c_chain_id_u256,
subnet_evm_chain_id_u256,
avax_asset_id,
tx_fee,
add_primary_network_validator_fee: ADD_PRIMARY_NETWORK_VALIDATOR_FEE,
create_subnet_tx_fee,
create_blockchain_tx_fee,
};
log::info!("initiated the wallet:\n{}", w);
Ok(w)
}
}
pub const ADD_PRIMARY_NETWORK_VALIDATOR_FEE: u64 = 0;
#[derive(Debug, Clone)]
pub struct Wallet<T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone> {
pub keychain: key::secp256k1::keychain::Keychain<T>,
pub ethers_signing_key: ethers_core::k256::ecdsa::SigningKey,
pub local_wallet: LocalWallet,
pub http_rpcs: Vec<String>,
pub http_rpc_idx: Arc<Mutex<usize>>,
pub network_id: u32,
pub network_name: String,
pub c_url_path: String,
pub subnet_evm_url_path: Option<String>,
pub c_providers: Vec<Provider<Http>>,
pub subnet_evm_providers: Option<Vec<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 subnet_evm_chain_id_u256: Option<U256>,
pub avax_asset_id: ids::Id,
pub tx_fee: u64,
pub add_primary_network_validator_fee: u64,
pub create_subnet_tx_fee: u64,
pub create_blockchain_tx_fee: u64,
}
impl<T> fmt::Display for Wallet<T>
where
T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "http_rpcs: {:?}\n", self.http_rpcs)?;
write!(f, "network_id: {}\n", self.network_id)?;
write!(f, "network_name: {}\n", self.network_name)?;
write!(f, "c_url_path: {}\n", self.c_url_path)?;
if let Some(v) = &self.subnet_evm_url_path {
write!(f, "subnet_evm_url_path: {}\n", v)?;
} else {
write!(f, "subnet_evm_url_path: None\n")?;
}
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)?;
if let Some(v) = self.subnet_evm_chain_id_u256 {
write!(f, "subnet_evm_chain_id_u256: {}\n", v)?;
} else {
write!(f, "subnet_evm_chain_id_u256: None\n")?;
}
write!(f, "avax_asset_id: {}\n", self.avax_asset_id)?;
write!(f, "tx_fee: {}\n", self.tx_fee)?;
write!(
f,
"add_primary_network_validator_fee: {}\n",
self.add_primary_network_validator_fee
)?;
write!(f, "create_subnet_tx_fee: {}\n", self.create_subnet_tx_fee)?;
write!(
f,
"create_blockchain_tx_fee: {}\n",
self.create_blockchain_tx_fee
)
}
}
impl<T> Wallet<T>
where
T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
{
pub fn pick_http_rpc(&self) -> (usize, String) {
let mut idx = self.http_rpc_idx.lock().unwrap();
let picked = *idx;
let http_rpc = self.http_rpcs[picked].clone();
*idx = (picked + 1) % self.http_rpcs.len();
log::debug!("picked http rpc {} at index {}", http_rpc, picked);
(picked, http_rpc)
}
#[must_use]
pub fn x(&self) -> x::X<T> {
x::X {
inner: self.clone(),
}
}
#[must_use]
pub fn p(&self) -> p::P<T> {
p::P {
inner: self.clone(),
}
}
#[cfg(feature = "evm_ethers")]
#[must_use]
pub fn evm_ethers_c(&self) -> evm_ethers::c::C<T> {
evm_ethers::c::C {
inner: self.clone(),
}
}
#[cfg(feature = "evm_ethers")]
#[must_use]
pub fn evm_ethers_subnet_evm(&self) -> evm_ethers::subnet_evm::SubnetEvm<T> {
assert!(
self.subnet_evm_url_path.is_some(),
"subnet-evm URL path is None"
);
assert!(
self.subnet_evm_providers.is_some(),
"subnet-evm providers is None"
);
assert!(
!self.subnet_evm_providers.clone().unwrap().is_empty(),
"subnet-evm providers are empty"
);
assert!(
self.subnet_evm_chain_id_u256.is_some(),
"subnet-evm chain Id is None"
);
evm_ethers::subnet_evm::SubnetEvm {
inner: self.clone(),
}
}
}