1pub mod p;
3pub mod x;
4
5#[cfg(feature = "wallet_evm")]
6#[cfg_attr(docsrs, doc(cfg(feature = "wallet_evm")))]
7pub mod evm;
8
9use std::{
10 fmt,
11 sync::{Arc, Mutex},
12};
13
14use crate::{
15 errors::Result,
16 ids::{self, short},
17 jsonrpc::client::{info as api_info, x as api_x},
18 key, utils,
19};
20
21#[derive(Debug, Clone)]
22pub struct Wallet<T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone> {
23 pub key_type: key::secp256k1::KeyType,
24 pub keychain: key::secp256k1::keychain::Keychain<T>,
25
26 pub base_http_urls: Vec<String>,
28 pub base_http_url_cursor: Arc<Mutex<usize>>, pub network_id: u32,
31 pub network_name: String,
32
33 pub x_address: String,
34 pub p_address: String,
35 pub short_address: short::Id,
36 pub eth_address: String,
37 pub h160_address: primitive_types::H160,
38
39 pub blockchain_id_x: ids::Id,
40 pub blockchain_id_p: ids::Id,
41
42 pub avax_asset_id: ids::Id,
43
44 pub tx_fee: u64,
46 pub add_primary_network_validator_fee: u64,
48 pub create_subnet_tx_fee: u64,
50 pub create_blockchain_tx_fee: u64,
52}
53
54impl<T> fmt::Display for Wallet<T>
58where
59 T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
60{
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 writeln!(f, "key_type: {}", self.key_type.as_str())?;
63 writeln!(f, "http_rpcs: {:?}", self.base_http_urls)?;
64 writeln!(f, "network_id: {}", self.network_id)?;
65 writeln!(f, "network_name: {}", self.network_name)?;
66
67 writeln!(f, "x_address: {}", self.x_address)?;
68 writeln!(f, "p_address: {}", self.p_address)?;
69 writeln!(f, "short_address: {}", self.short_address)?;
70 writeln!(f, "eth_address: {}", self.eth_address)?;
71 writeln!(f, "h160_address: {}", self.h160_address)?;
72
73 writeln!(f, "blockchain_id_x: {}", self.blockchain_id_x)?;
74 writeln!(f, "blockchain_id_p: {}", self.blockchain_id_p)?;
75
76 writeln!(f, "avax_asset_id: {}", self.avax_asset_id)?;
77
78 writeln!(f, "tx_fee: {}", self.tx_fee)?;
79 writeln!(
80 f,
81 "add_primary_network_validator_fee: {}",
82 self.add_primary_network_validator_fee
83 )?;
84 writeln!(f, "create_subnet_tx_fee: {}", self.create_subnet_tx_fee)?;
85 writeln!(
86 f,
87 "create_blockchain_tx_fee: {}",
88 self.create_blockchain_tx_fee
89 )
90 }
91}
92
93impl<T> Wallet<T>
94where
95 T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
96{
97 pub fn pick_base_http_url(&self) -> (usize, String) {
100 let mut idx = self.base_http_url_cursor.lock().unwrap();
101
102 let picked = *idx;
103 let http_rpc = self.base_http_urls[picked].clone();
104 *idx = (picked + 1) % self.base_http_urls.len();
105
106 log::debug!("picked base http URL {http_rpc} at index {picked}");
107 (picked, http_rpc)
108 }
109}
110
111#[derive(Debug, Clone)]
112pub struct Builder<T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone> {
113 pub key: T,
114 pub base_http_urls: Vec<String>,
115 pub only_evm: bool,
116}
117
118impl<T> Builder<T>
119where
120 T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
121{
122 pub fn new(key: &T) -> Self {
123 Self {
124 key: key.clone(),
125 base_http_urls: Vec::new(),
126 only_evm: false,
127 }
128 }
129
130 #[must_use]
133 pub fn base_http_url(mut self, u: String) -> Self {
134 let (scheme, host, port, _, _) =
135 utils::urls::extract_scheme_host_port_path_chain_alias(&u).unwrap();
136 let scheme = if let Some(s) = scheme {
137 format!("{s}://")
138 } else {
139 String::new()
140 };
141 let rpc_ep = format!("{scheme}{host}");
142 let rpc_url = if let Some(port) = port {
143 format!("{rpc_ep}:{port}")
144 } else {
145 rpc_ep.clone() };
147
148 if self.base_http_urls.is_empty() {
149 self.base_http_urls = vec![rpc_url];
150 } else {
151 self.base_http_urls.push(rpc_url);
152 }
153 self
154 }
155
156 #[must_use]
157 pub fn only_evm(mut self) -> Self {
158 self.only_evm = true;
159 self
160 }
161
162 #[must_use]
165 pub fn base_http_urls(mut self, urls: Vec<String>) -> Self {
166 let mut base_http_urls = Vec::new();
167 for http_rpc in urls.iter() {
168 let (scheme, host, port, _, _) =
169 utils::urls::extract_scheme_host_port_path_chain_alias(http_rpc).unwrap();
170 let scheme = if let Some(s) = scheme {
171 format!("{s}://")
172 } else {
173 String::new()
174 };
175 let rpc_ep = format!("{scheme}{host}");
176 let rpc_url = if let Some(port) = port {
177 format!("{rpc_ep}:{port}")
178 } else {
179 rpc_ep.clone() };
181
182 base_http_urls.push(rpc_url);
183 }
184 self.base_http_urls = base_http_urls;
185 self
186 }
187
188 pub async fn build(&self) -> Result<Wallet<T>> {
189 log::info!(
190 "building wallet with {} endpoints",
191 self.base_http_urls.len()
192 );
193
194 let keychain = key::secp256k1::keychain::Keychain::new(vec![self.key.clone()]);
195 let h160_address = keychain.keys[0].h160_address();
196
197 let (
198 network_id,
199 network_name,
200 blockchain_id_x,
201 blockchain_id_p,
202 avax_asset_id,
203 tx_fee,
204 create_subnet_tx_fee,
205 create_blockchain_tx_fee,
206 ) = if self.only_evm {
207 log::warn!("wallet is only used for EVM thus skipping querying info API");
208 (
209 0,
210 String::new(),
211 ids::Id::empty(),
212 ids::Id::empty(),
213 ids::Id::empty(),
214 0,
215 0,
216 0,
217 )
218 } else {
219 let resp = api_info::get_network_id(&self.base_http_urls[0]).await?;
220 let network_id = resp.result.unwrap().network_id;
221 let resp = api_info::get_network_name(&self.base_http_urls[0]).await?;
222 let network_name = resp.result.unwrap().network_name;
223
224 let resp = api_info::get_blockchain_id(&self.base_http_urls[0], "X").await?;
225 let blockchain_id_x = resp.result.unwrap().blockchain_id;
226
227 let resp = api_info::get_blockchain_id(&self.base_http_urls[0], "P").await?;
228 let blockchain_id_p = resp.result.unwrap().blockchain_id;
229
230 let resp = api_x::get_asset_description(&self.base_http_urls[0], "AVAX").await?;
231 let resp = resp
232 .result
233 .expect("unexpected None GetAssetDescriptionResult");
234 let avax_asset_id = resp.asset_id;
235
236 let resp = api_info::get_tx_fee(&self.base_http_urls[0]).await?;
237 let get_tx_fee_result = resp.result.unwrap();
238 let tx_fee = get_tx_fee_result.tx_fee;
239 let create_subnet_tx_fee = get_tx_fee_result.create_subnet_tx_fee;
240 let create_blockchain_tx_fee = get_tx_fee_result.create_blockchain_tx_fee;
241
242 (
243 network_id,
244 network_name,
245 blockchain_id_x,
246 blockchain_id_p,
247 avax_asset_id,
248 tx_fee,
249 create_subnet_tx_fee,
250 create_blockchain_tx_fee,
251 )
252 };
253
254 let w = Wallet {
255 key_type: self.key.key_type(),
256 keychain,
257
258 base_http_urls: self.base_http_urls.clone(),
259 base_http_url_cursor: Arc::new(Mutex::new(0)),
260
261 network_id,
262 network_name,
263
264 x_address: self.key.hrp_address(network_id, "X").unwrap(),
265 p_address: self.key.hrp_address(network_id, "P").unwrap(),
266 short_address: self.key.short_address().unwrap(),
267 eth_address: self.key.eth_address(),
268 h160_address,
269
270 blockchain_id_x,
271 blockchain_id_p,
272
273 avax_asset_id,
274
275 tx_fee,
276 add_primary_network_validator_fee: ADD_PRIMARY_NETWORK_VALIDATOR_FEE,
277 create_subnet_tx_fee,
278 create_blockchain_tx_fee,
279 };
280 log::info!("initiated the wallet:\n{}", w);
281
282 Ok(w)
283 }
284}
285
286pub const ADD_PRIMARY_NETWORK_VALIDATOR_FEE: u64 = 0;