Skip to main content

avalanche_types/wallet/evm/
mod.rs

1pub mod eip1559;
2
3use std::{ops::Div, sync::Arc, time::Duration};
4
5use crate::{
6    errors::{Error, Result},
7    jsonrpc::client::evm as jsonrpc_client_evm,
8    key, wallet,
9};
10use ethers::{
11    prelude::{
12        gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
13        NonceManagerMiddleware, SignerMiddleware,
14    },
15    utils::Units::Gwei,
16};
17use ethers_providers::{Http, HttpRateLimitRetryPolicy, Provider, RetryClient};
18use lazy_static::lazy_static;
19use primitive_types::U256;
20use reqwest::ClientBuilder;
21use url::Url;
22
23#[derive(Clone, Debug)]
24pub struct Evm<T, S>
25where
26    T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
27    S: ethers_signers::Signer + Clone,
28    S::Error: 'static,
29{
30    pub inner: wallet::Wallet<T>,
31
32    pub chain_rpc_url: String,
33    pub provider: Arc<Provider<RetryClient<Http>>>,
34
35    pub eth_signer: S,
36
37    /// Middleware created on the picked RPC endpoint and signer address.
38    /// ref. "ethers-middleware::signer::SignerMiddleware"
39    /// ref. "ethers-signers::LocalWallet"
40    /// ref. "ethers-signers::wallet::Wallet"
41    /// ref. "ethers-signers::wallet::Wallet::sign_transaction_sync"
42    /// ref. <https://github.com/giantbrain0216/ethers_rs/blob/master/ethers-middleware/tests/nonce_manager.rs>
43    #[allow(clippy::type_complexity)]
44    pub middleware: Arc<
45        NonceManagerMiddleware<
46            SignerMiddleware<GasEscalatorMiddleware<Arc<Provider<RetryClient<Http>>>>, S>,
47        >,
48    >,
49
50    pub chain_id: U256,
51}
52
53impl<T, S> Evm<T, S>
54where
55    T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
56    S: ethers_signers::Signer + Clone,
57    S::Error: 'static,
58{
59    /// Fetches the current balance of the wallet owner.
60    pub async fn balance(&self) -> Result<U256> {
61        let cur_balance =
62            jsonrpc_client_evm::get_balance(&self.chain_rpc_url, self.inner.h160_address).await?;
63        Ok(cur_balance)
64    }
65}
66
67impl<T> wallet::Wallet<T>
68where
69    T: key::secp256k1::ReadOnly + key::secp256k1::SignOnly + Clone,
70{
71    /// Sets the chain RPC URLs (can be different than base HTTP URLs).
72    /// e.g., "{base_http_url}/ext/bc/{chain_id_alias}/rpc"
73    /// Set "chain_id_alias" to either "C" or subnet-evm chain Id.
74    pub fn evm<S>(&self, eth_signer: &S, chain_rpc_url: &str, chain_id: U256) -> Result<Evm<T, S>>
75    where
76        S: ethers_signers::Signer + Clone,
77        S::Error: 'static,
78    {
79        // TODO: make timeouts + retries configurable
80        let provider = new_provider(
81            chain_rpc_url,
82            Duration::from_secs(15),
83            Duration::from_secs(30),
84            5,
85            Duration::from_secs(3),
86        )?;
87        let provider_arc = Arc::new(provider);
88
89        let nonce_middleware = new_middleware(Arc::clone(&provider_arc), eth_signer, chain_id)?;
90        let middleware = Arc::new(nonce_middleware);
91
92        Ok(Evm::<T, S> {
93            inner: self.clone(),
94
95            chain_rpc_url: chain_rpc_url.to_string(),
96            provider: provider_arc,
97
98            eth_signer: eth_signer.clone(),
99
100            middleware,
101
102            chain_id,
103        })
104    }
105}
106
107/// Make sure to not create multiple providers for the ease of nonce management.
108/// ref. "`Provider::<RetryClient<Http>>::new_client`".
109pub fn new_provider(
110    chain_rpc_url: &str,
111    connect_timeout: Duration,
112    request_timeout: Duration,
113    max_retries: u32,
114    backoff_timeout: Duration,
115) -> Result<Provider<RetryClient<Http>>> {
116    let u = Url::parse(chain_rpc_url).map_err(|e| Error::Other {
117        message: format!("failed to parse chain RPC URL {}", e),
118        retryable: false,
119    })?;
120
121    let http_cli = ClientBuilder::new()
122        .user_agent(env!("CARGO_PKG_NAME"))
123        .connect_timeout(connect_timeout)
124        .connection_verbose(true)
125        .timeout(request_timeout)
126        .danger_accept_invalid_certs(true) // make this configurable
127        .build()
128        .map_err(|e| {
129            // TODO: check retryable
130            Error::Other {
131                message: format!("failed reqwest::ClientBuilder.build '{}'", e),
132                retryable: false,
133            }
134        })?;
135
136    // TODO: make "HttpRateLimitRetryPolicy" configurable
137    let retry_client = RetryClient::new(
138        Http::new_with_client(u, http_cli),
139        Box::new(HttpRateLimitRetryPolicy),
140        max_retries,
141        backoff_timeout.as_millis() as u64,
142    );
143
144    let provider = Provider::new(retry_client).interval(Duration::from_millis(2000u64));
145    Ok(provider)
146}
147
148/// Make sure to not create multiple providers for the ease of nonce management.
149#[allow(clippy::type_complexity)]
150pub fn new_middleware<S>(
151    provider: Arc<Provider<RetryClient<Http>>>,
152    eth_signer: &S,
153    chain_id: U256,
154) -> Result<
155    NonceManagerMiddleware<
156        SignerMiddleware<GasEscalatorMiddleware<Arc<Provider<RetryClient<Http>>>>, S>,
157    >,
158>
159where
160    S: ethers_signers::Signer + Clone,
161    S::Error: 'static,
162{
163    // TODO: make this configurable
164    let escalator = GeometricGasPrice::new(5.0, 10u64, None::<u64>);
165
166    // TODO: this can lead to file descriptor leaks!!!
167    // ref. <https://github.com/gakonst/ethers-rs/issues/2269>
168    let gas_escalator_middleware =
169        GasEscalatorMiddleware::new(Arc::clone(&provider), escalator, Frequency::PerBlock);
170
171    let signer_middleware = SignerMiddleware::new(
172        gas_escalator_middleware,
173        eth_signer.clone().with_chain_id(chain_id.as_u64()),
174    );
175
176    let nonce_middleware = NonceManagerMiddleware::new(signer_middleware, eth_signer.address());
177    Ok(nonce_middleware)
178}
179
180lazy_static! {
181    pub static ref GWEI: U256 = U256::from(10).checked_pow(Gwei.as_num().into()).unwrap();
182}
183
184/// Converts WEI to GWEI.
185pub fn wei_to_gwei(wei: impl Into<U256>) -> U256 {
186    let wei: U256 = wei.into();
187    if wei.is_zero() {
188        U256::zero()
189    } else {
190        wei.div(*GWEI)
191    }
192}