avalanche_types/wallet/
mod.rs

1//! Wallets for Avalanche.
2pub 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    /// Base HTTP URLs without RPC endpoint path.
27    pub base_http_urls: Vec<String>,
28    pub base_http_url_cursor: Arc<Mutex<usize>>, // to roundrobin
29
30    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    /// Fee that is burned by every non-state creating transaction.
45    pub tx_fee: u64,
46    /// Transaction fee for adding a primary network validator.
47    pub add_primary_network_validator_fee: u64,
48    /// Transaction fee to create a new subnet.
49    pub create_subnet_tx_fee: u64,
50    /// Transaction fee to create a new blockchain.
51    pub create_blockchain_tx_fee: u64,
52}
53
54/// ref. <https://doc.rust-lang.org/std/string/trait.ToString.html>
55/// ref. <https://doc.rust-lang.org/std/fmt/trait.Display.html>
56/// Use "Self.to_string()" to directly invoke this.
57impl<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    /// Picks one endpoint in roundrobin, and updates the cursor for next calls.
98    /// Returns the pair of an index and its corresponding endpoint.
99    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    /// Adds an HTTP rpc endpoint to the `http_rpcs` field in the Builder.
131    /// If URL path is specified, it strips the URL path.
132    #[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() // e.g., DNS
146        };
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    /// Overwrites the HTTP rpc endpoints to the `urls` field in the Builder.
163    /// If URL path is specified, it strips the URL path.
164    #[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() // e.g., DNS
180            };
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
286/// ref. <https://docs.avax.network/learn/platform-overview/transaction-fees/#fee-schedule>
287pub const ADD_PRIMARY_NETWORK_VALIDATOR_FEE: u64 = 0;