Repository: gakonst/ethers-rs
Pull request #1192 (2022-04-28 15:38:50)

fix(solc): flatten import aliases
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation

<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->
addresses [foundry#1440](https://github.com/foundry-rs/foundry/issues/1440)

## Solution

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

This PR includes: 
- More robust regex for import statement parsing in case of `solang` failure
- Partial support for import aliases: any explicit import alias will be replaced with its original name 

Known Issues: 
- Since alias replacement rewrites the contents of the file, any previous data unit location might be incorrect. This is a temporary solution and is based on the assumption that any usage of an alias happens after all of the import statements. 

## PR Checklist

- [x] Added Tests
- [x] Added Documentation
- [ ] Updated the changelog



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 13570658..5def5468 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,7 @@
 
 ### Unreleased
 
-- Fix RLP decoding of `from` field for `Eip1559TransactionRequest` and 
+- Fix RLP decoding of `from` field for `Eip1559TransactionRequest` and
   `Eip2930TransactionRequest`, remove `Eip1559TransactionRequest` `sighash`
   method [1180](https://github.com/gakonst/ethers-rs/pull/1180)
 - Fix RLP encoding of absent access list in `Transaction` [1137](https://github.com/gakonst/ethers-rs/pull/1137)
@@ -62,6 +62,7 @@
   [#996](https://github.com/gakonst/ethers-rs/pull/996)
 - Add `TransactionReceipt::to` and `TransactionReceipt::from`
   [#1184](https://github.com/gakonst/ethers-rs/pull/1184)
+- Add `From<H160>` and From<Vec<H160>> traits to `ValueOrArray<H160>` [#1199](https://github.com/gakonst/ethers-rs/pull/1200)
 
 ## ethers-contract-abigen
 
diff --git a/Cargo.lock b/Cargo.lock
index 077ea560..6909794e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1813,9 +1813,9 @@ dependencies = [
 
 [[package]]
 name = "http"
-version = "0.2.6"
+version = "0.2.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"
+checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb"
 dependencies = [
  "bytes",
  "fnv",
@@ -3063,9 +3063,9 @@ dependencies = [
 
 [[package]]
 name = "rusoto_core"
-version = "0.47.0"
+version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b4f000e8934c1b4f70adde180056812e7ea6b1a247952db8ee98c94cd3116cc"
+checksum = "1db30db44ea73551326269adcf7a2169428a054f14faf9e1768f2163494f2fa2"
 dependencies = [
  "async-trait",
  "base64 0.13.0",
@@ -3088,9 +3088,9 @@ dependencies = [
 
 [[package]]
 name = "rusoto_credential"
-version = "0.47.0"
+version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a46b67db7bb66f5541e44db22b0a02fed59c9603e146db3a9e633272d3bac2f"
+checksum = "ee0a6c13db5aad6047b6a44ef023dbbc21a056b6dab5be3b79ce4283d5c02d05"
 dependencies = [
  "async-trait",
  "chrono",
@@ -3106,9 +3106,9 @@ dependencies = [
 
 [[package]]
 name = "rusoto_kms"
-version = "0.47.0"
+version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7892cd2cca7644d33bd6fafdb2236efd3659162fd7b73ca68d3877f0528399c"
+checksum = "3e1fc19cfcfd9f6b2f96e36d5b0dddda9004d2cbfc2d17543e3b9f10cc38fce8"
 dependencies = [
  "async-trait",
  "bytes",
@@ -3120,9 +3120,9 @@ dependencies = [
 
 [[package]]
 name = "rusoto_signature"
-version = "0.47.0"
+version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6264e93384b90a747758bcc82079711eacf2e755c3a8b5091687b5349d870bcc"
+checksum = "a5ae95491c8b4847931e291b151127eccd6ff8ca13f33603eb3d0035ecb05272"
 dependencies = [
  "base64 0.13.0",
  "bytes",
@@ -3293,9 +3293,9 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.7"
+version = "1.0.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4"
+checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd"
 dependencies = [
  "serde",
 ]
@@ -3652,9 +3652,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "1.0.91"
+version = "1.0.92"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
+checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -3740,18 +3740,18 @@ checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
 
 [[package]]
 name = "thiserror"
-version = "1.0.30"
+version = "1.0.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
+checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.30"
+version = "1.0.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
+checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
 dependencies = [
  "proc-macro2",
  "quote",
diff --git a/ethers-contract/Cargo.toml b/ethers-contract/Cargo.toml
index f66bd160..554e2971 100644
--- a/ethers-contract/Cargo.toml
+++ b/ethers-contract/Cargo.toml
@@ -18,7 +18,7 @@ ethers-derive-eip712 = { version = "^0.2.0", path = "../ethers-core/ethers-deriv
 
 serde = { version = "1.0.124", default-features = false }
 serde_json = { version = "1.0.64", default-features = false }
-thiserror = { version = "1.0.30", default-features = false }
+thiserror = { version = "1.0.31", default-features = false }
 once_cell = { version = "1.10.0" }
 pin-project = {version = "1.0.7", default-features = false }
 futures-util = { version = "^0.3" }
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index bd11e44e..1f7212dc 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -25,7 +25,7 @@ tiny-keccak = { version = "2.0.2", default-features = false }
 # misc
 serde = { version = "1.0.124", default-features = false, features = ["derive"] }
 serde_json = { version = "1.0.64", default-features = false }
-thiserror = { version = "1.0.30", default-features = false }
+thiserror = { version = "1.0.31", default-features = false }
 bytes = { version = "1.1.0", features = ["serde"] }
 hex = { version = "0.4.3", default-features = false, features = ["std"] }
 once_cell = { version = "1.10.0", optional = true }
@@ -35,7 +35,7 @@ cargo_metadata = { version = "0.14.2", optional = true }
 
 # eip712 feature enabled dependencies
 convert_case = { version = "0.5.0", optional = true }
-syn = { version = "1.0.91", optional = true }
+syn = { version = "1.0.92", optional = true }
 proc-macro2 = { version = "1.0.37", optional = true }
 
 [dev-dependencies]
diff --git a/ethers-core/src/types/log.rs b/ethers-core/src/types/log.rs
index 3815f158..89758434 100644
--- a/ethers-core/src/types/log.rs
+++ b/ethers-core/src/types/log.rs
@@ -1,6 +1,6 @@
 // Adapted from https://github.com/tomusdrw/rust-web3/blob/master/src/types/log.rs
 use crate::{
-    types::{Address, BlockNumber, Bytes, H256, U256, U64},
+    types::{Address, BlockNumber, Bytes, H160, H256, U256, U64},
     utils::keccak256,
 };
 use serde::{
@@ -306,7 +306,31 @@ impl Filter {
         self.block_option = self.block_option.set_hash(hash.into());
         self
     }
-
+    /// Sets the inner filter object
+    ///
+    /// *NOTE:* ranges are always inclusive
+    ///
+    /// # Examples
+    ///
+    /// Match only a specific address `("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF")`
+    ///
+    /// ```rust
+    /// # use ethers_core::types::{Filter, Address};
+    /// # fn main() {
+    /// let filter = Filter::new().address("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::<Address>().unwrap());
+    /// # }
+    /// ```
+    ///
+    /// Match all addresses in array `(vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF",
+    /// "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8"])`
+    ///
+    /// ```rust
+    /// # use ethers_core::types::{Filter, Address, ValueOrArray};
+    /// # fn main() {
+    /// let addresses = vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::<Address>().unwrap(),"0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".parse::<Address>().unwrap()];
+    /// let filter = Filter::new().address(addresses);
+    /// # }
+    /// ```
     #[must_use]
     pub fn address<T: Into<ValueOrArray<Address>>>(mut self, address: T) -> Self {
         self.address = Some(address.into());
@@ -360,6 +384,18 @@ pub enum ValueOrArray<T> {
 
 // TODO: Implement more common types - or adjust this to work with all Tokenizable items
 
+impl From<H160> for ValueOrArray<H160> {
+    fn from(src: H160) -> Self {
+        ValueOrArray::Value(src)
+    }
+}
+
+impl From<Vec<H160>> for ValueOrArray<H160> {
+    fn from(src: Vec<H160>) -> Self {
+        ValueOrArray::Array(src)
+    }
+}
+
 impl From<H256> for ValueOrArray<H256> {
     fn from(src: H256) -> Self {
         ValueOrArray::Value(src)
diff --git a/ethers-etherscan/Cargo.toml b/ethers-etherscan/Cargo.toml
index 2d9e7985..008a69b7 100644
--- a/ethers-etherscan/Cargo.toml
+++ b/ethers-etherscan/Cargo.toml
@@ -20,7 +20,7 @@ reqwest = { version = "0.11.10", default-features = false, features = ["json"] }
 serde = { version = "1.0.124", default-features = false, features = ["derive"] }
 serde_json = { version = "1.0.64", default-features = false }
 serde-aux = { version = "3.0.1", default-features = false }
-thiserror = "1.0.29"
+thiserror = "1.0.31"
 tracing = "0.1.34"
 
 [dev-dependencies]
diff --git a/ethers-etherscan/src/contract.rs b/ethers-etherscan/src/contract.rs
index a2d497d9..b390a5ab 100644
--- a/ethers-etherscan/src/contract.rs
+++ b/ethers-etherscan/src/contract.rs
@@ -10,7 +10,7 @@ use crate::{
 };
 
 /// Arguments for verifying contracts
-#[derive(Debug, Clone, Serialize)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct VerifyContract {
     #[serde(rename = "contractaddress")]
     pub address: Address,
@@ -26,13 +26,15 @@ pub struct VerifyContract {
     pub compiler_version: String,
     /// applicable when codeformat=solidity-single-file
     #[serde(rename = "optimizationUsed", skip_serializing_if = "Option::is_none")]
-    optimization_used: Option<String>,
+    pub optimization_used: Option<String>,
+    /// applicable when codeformat=solidity-single-file
     #[serde(skip_serializing_if = "Option::is_none")]
     pub runs: Option<String>,
     /// NOTE: there is a typo in the etherscan API `constructorArguements`
     #[serde(rename = "constructorArguements", skip_serializing_if = "Option::is_none")]
     pub constructor_arguments: Option<String>,
-    #[serde(rename = "evmversion")]
+    /// applicable when codeformat=solidity-single-file
+    #[serde(rename = "evmversion", skip_serializing_if = "Option::is_none")]
     pub evm_version: Option<String>,
     #[serde(flatten)]
     pub other: HashMap<String, String>,
@@ -114,7 +116,7 @@ impl VerifyContract {
     }
 }
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
 pub enum CodeFormat {
     #[serde(rename = "solidity-single-file")]
     SingleFile,
diff --git a/ethers-etherscan/src/errors.rs b/ethers-etherscan/src/errors.rs
index d70f632c..c92e43e1 100644
--- a/ethers-etherscan/src/errors.rs
+++ b/ethers-etherscan/src/errors.rs
@@ -31,4 +31,6 @@ pub enum EtherscanError {
     LocalNetworksNotSupported,
     #[error("Unknown error: {0}")]
     Unknown(String),
+    #[error("Missing field: {0}")]
+    Builder(String),
 }
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index e5b6f1a1..4b9b8de1 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -8,7 +8,7 @@ use std::{
 };
 
 use contract::ContractMetadata;
-use reqwest::{header, Url};
+use reqwest::{header, IntoUrl, Url};
 use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use tracing::trace;
 
@@ -42,82 +42,22 @@ pub struct Client {
     cache: Option<Cache>,
 }
 
-/// A wrapper around an Etherscan cache object with an expiry
-#[derive(Clone, Debug, Deserialize, Serialize)]
-struct CacheEnvelope<T> {
-    expiry: u64,
-    data: T,
-}
-
-/// Simple cache for etherscan requests
-#[derive(Clone, Debug)]
-struct Cache {
-    root: PathBuf,
-    ttl: Duration,
-}
-
-impl Cache {
-    fn new(root: PathBuf, ttl: Duration) -> Self {
-        Self { root, ttl }
-    }
-
-    fn get_abi(&self, address: Address) -> Option<Option<ethers_core::abi::Abi>> {
-        self.get("abi", address)
-    }
-
-    fn set_abi(&self, address: Address, abi: Option<&Abi>) {
-        self.set("abi", address, abi)
-    }
-
-    fn get_source(&self, address: Address) -> Option<Option<ContractMetadata>> {
-        self.get("sources", address)
-    }
-
-    fn set_source(&self, address: Address, source: Option<&ContractMetadata>) {
-        self.set("sources", address, source)
-    }
-
-    fn set<T: Serialize>(&self, prefix: &str, address: Address, item: T) {
-        let path = self.root.join(prefix).join(format!("{:?}.json", address));
-        let writer = std::fs::File::create(path).ok().map(std::io::BufWriter::new);
-        if let Some(mut writer) = writer {
-            let _ = serde_json::to_writer(
-                &mut writer,
-                &CacheEnvelope {
-                    expiry: SystemTime::now()
-                        .checked_add(self.ttl)
-                        .expect("cache ttl overflowed")
-                        .duration_since(UNIX_EPOCH)
-                        .expect("system time is before unix epoch")
-                        .as_secs(),
-                    data: item,
-                },
-            );
-            let _ = writer.flush();
-        }
-    }
-
-    fn get<T: DeserializeOwned>(&self, prefix: &str, address: Address) -> Option<T> {
-        let path = self.root.join(prefix).join(format!("{:?}.json", address));
-        let reader = std::io::BufReader::new(std::fs::File::open(path).ok()?);
-        if let Ok(inner) = serde_json::from_reader::<_, CacheEnvelope<T>>(reader) {
-            // If this does not return None then we have passed the expiry
-            if SystemTime::now()
-                .duration_since(UNIX_EPOCH)
-                .expect("system time is before unix epoch")
-                .checked_sub(Duration::from_secs(inner.expiry))
-                .is_some()
-            {
-                return None
-            }
-
-            return Some(inner.data)
-        }
-        None
+impl Client {
+    /// Creates a `ClientBuilder` to configure a `Client`.
+    /// This is the same as `ClientBuilder::default()`.
+    ///
+    /// # Example
+    ///
+    /// ```rust
+    /// use ethers_core::types::Chain;
+    /// use ethers_etherscan::Client;
+    /// let client = Client::builder().with_api_key("<API KEY>").chain(Chain::Mainnet).unwrap().build().unwrap();
+    /// ```
+    pub fn builder() -> ClientBuilder {
+        ClientBuilder::default()
     }
-}
 
-impl Client {
+    /// Creates a new instance that caches etherscan requests
     pub fn new_cached(
         chain: Chain,
         api_key: impl Into<String>,
@@ -131,76 +71,7 @@ impl Client {
 
     /// Create a new client with the correct endpoints based on the chain and provided API key
     pub fn new(chain: Chain, api_key: impl Into<String>) -> Result<Self> {
-        let (etherscan_api_url, etherscan_url) = match chain {
-            Chain::Mainnet => {
-                (Url::parse("https://api.etherscan.io/api"), Url::parse("https://etherscan.io"))
-            }
-            Chain::Ropsten | Chain::Kovan | Chain::Rinkeby | Chain::Goerli => {
-                let chain_name = chain.to_string().to_lowercase();
-
-                (
-                    Url::parse(&format!("https://api-{}.etherscan.io/api", chain_name)),
-                    Url::parse(&format!("https://{}.etherscan.io", chain_name)),
-                )
-            }
-            Chain::Polygon => (
-                Url::parse("https://api.polygonscan.com/api"),
-                Url::parse("https://polygonscan.com"),
-            ),
-            Chain::PolygonMumbai => (
-                Url::parse("https://api-testnet.polygonscan.com/api"),
-                Url::parse("https://mumbai.polygonscan.com"),
-            ),
-            Chain::Avalanche => {
-                (Url::parse("https://api.snowtrace.io/api"), Url::parse("https://snowtrace.io"))
-            }
-            Chain::AvalancheFuji => (
-                Url::parse("https://api-testnet.snowtrace.io/api"),
-                Url::parse("https://testnet.snowtrace.io"),
-            ),
-            Chain::Optimism => (
-                Url::parse("https://api-optimistic.etherscan.io/api"),
-                Url::parse("https://optimistic.etherscan.io"),
-            ),
-            Chain::OptimismKovan => (
-                Url::parse("https://api-kovan-optimistic.etherscan.io/api"),
-                Url::parse("https://kovan-optimistic.etherscan.io"),
-            ),
-            Chain::Fantom => {
-                (Url::parse("https://api.ftmscan.com/api"), Url::parse("https://ftmscan.com"))
-            }
-            Chain::FantomTestnet => (
-                Url::parse("https://api-testnet.ftmscan.com/api"),
-                Url::parse("https://testnet.ftmscan.com"),
-            ),
-            Chain::BinanceSmartChain => {
-                (Url::parse("https://api.bscscan.com/api"), Url::parse("https://bscscan.com"))
-            }
-            Chain::BinanceSmartChainTestnet => (
-                Url::parse("https://api-testnet.bscscan.com/api"),
-                Url::parse("https://testnet.bscscan.com"),
-            ),
-            Chain::Arbitrum => {
-                (Url::parse("https://api.arbiscan.io/api"), Url::parse("https://arbiscan.io"))
-            }
-            Chain::ArbitrumTestnet => (
-                Url::parse("https://api-testnet.arbiscan.io/api"),
-                Url::parse("https://testnet.arbiscan.io"),
-            ),
-            Chain::Cronos => {
-                (Url::parse("https://api.cronoscan.com/api"), Url::parse("https://cronoscan.com"))
-            }
-            Chain::Dev => return Err(EtherscanError::LocalNetworksNotSupported),
-            chain => return Err(EtherscanError::ChainNotSupported(chain)),
-        };
-
-        Ok(Self {
-            client: Default::default(),
-            api_key: api_key.into(),
-            etherscan_api_url: etherscan_api_url.expect("is valid http"),
-            etherscan_url: etherscan_url.expect("is valid http"),
-            cache: None,
-        })
+        Client::builder().with_api_key(api_key).chain(chain)?.build()
     }
 
     /// Create a new client with the correct endpoints based on the chain and API key
@@ -319,6 +190,216 @@ impl Client {
     }
 }
 
+#[derive(Clone, Debug, Default)]
+pub struct ClientBuilder {
+    /// Client that executes HTTP requests
+    client: Option<reqwest::Client>,
+    /// Etherscan API key
+    api_key: Option<String>,
+    /// Etherscan API endpoint like <https://api(-chain).etherscan.io/api>
+    etherscan_api_url: Option<Url>,
+    /// Etherscan base endpoint like <https://etherscan.io>
+    etherscan_url: Option<Url>,
+    /// Path to where ABI files should be cached
+    cache: Option<Cache>,
+}
+
+// === impl ClientBuilder ===
+
+impl ClientBuilder {
+    /// Configures the etherscan url and api url for the given chain
+    ///
+    /// # Errors
+    ///
+    /// Fails if the chain is not supported by etherscan
+    pub fn chain(self, chain: Chain) -> Result<Self> {
+        fn urls(
+            api: impl IntoUrl,
+            url: impl IntoUrl,
+        ) -> (reqwest::Result<Url>, reqwest::Result<Url>) {
+            (api.into_url(), url.into_url())
+        }
+
+        let (etherscan_api_url, etherscan_url) = match chain {
+            Chain::Mainnet => urls("https://api.etherscan.io/api", "https://etherscan.io"),
+            Chain::Ropsten | Chain::Kovan | Chain::Rinkeby | Chain::Goerli => {
+                let chain_name = chain.to_string().to_lowercase();
+                urls(
+                    format!("https://api-{}.etherscan.io/api", chain_name),
+                    format!("https://{}.etherscan.io", chain_name),
+                )
+            }
+            Chain::Polygon => urls("https://api.polygonscan.com/api", "https://polygonscan.com"),
+            Chain::PolygonMumbai => {
+                urls("https://api-testnet.polygonscan.com/api", "https://mumbai.polygonscan.com")
+            }
+            Chain::Avalanche => urls("https://api.snowtrace.io/api", "https://snowtrace.io"),
+            Chain::AvalancheFuji => {
+                urls("https://api-testnet.snowtrace.io/api", "https://testnet.snowtrace.io")
+            }
+            Chain::Optimism => {
+                urls("https://api-optimistic.etherscan.io/api", "https://optimistic.etherscan.io")
+            }
+            Chain::OptimismKovan => urls(
+                "https://api-kovan-optimistic.etherscan.io/api",
+                "https://kovan-optimistic.etherscan.io",
+            ),
+            Chain::Fantom => urls("https://api.ftmscan.com/api", "https://ftmscan.com"),
+            Chain::FantomTestnet => {
+                urls("https://api-testnet.ftmscan.com/api", "https://testnet.ftmscan.com")
+            }
+            Chain::BinanceSmartChain => urls("https://api.bscscan.com/api", "https://bscscan.com"),
+            Chain::BinanceSmartChainTestnet => {
+                urls("https://api-testnet.bscscan.com/api", "https://testnet.bscscan.com")
+            }
+            Chain::Arbitrum => urls("https://api.arbiscan.io/api", "https://arbiscan.io"),
+            Chain::ArbitrumTestnet => {
+                urls("https://api-testnet.arbiscan.io/api", "https://testnet.arbiscan.io")
+            }
+            Chain::Cronos => urls("https://api.cronoscan.com/api", "https://cronoscan.com"),
+            Chain::Dev => return Err(EtherscanError::LocalNetworksNotSupported),
+            chain => return Err(EtherscanError::ChainNotSupported(chain)),
+        };
+        self.with_api_url(etherscan_api_url?)?.with_url(etherscan_url?)
+    }
+
+    /// Configures the etherscan url
+    ///
+    /// # Errors
+    ///
+    /// Fails if the `etherscan_url` is not a valid `Url`
+    pub fn with_url(mut self, etherscan_url: impl IntoUrl) -> Result<Self> {
+        self.etherscan_url = Some(etherscan_url.into_url()?);
+        Ok(self)
+    }
+
+    /// Configures the `reqwest::Client`
+    pub fn with_client(mut self, client: reqwest::Client) -> Self {
+        self.client = Some(client);
+        self
+    }
+
+    /// Configures the etherscan api url
+    ///
+    /// # Errors
+    ///
+    /// Fails if the `etherscan_api_url` is not a valid `Url`
+    pub fn with_api_url(mut self, etherscan_api_url: impl IntoUrl) -> Result<Self> {
+        self.etherscan_api_url = Some(etherscan_api_url.into_url()?);
+        Ok(self)
+    }
+
+    /// Configures the etherscan api key
+    pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
+        self.api_key = Some(api_key.into());
+        self
+    }
+
+    /// Configures cache for etherscan request
+    pub fn with_cache(mut self, cache_root: Option<PathBuf>, cache_ttl: Duration) -> Self {
+        self.cache = cache_root.map(|root| Cache::new(root, cache_ttl));
+        self
+    }
+
+    /// Returns a Client that uses this ClientBuilder configuration.
+    ///
+    /// # Errors
+    /// if required fields are missing:
+    ///   - `api_key`
+    ///   - `etherscan_api_url`
+    ///   - `etherscan_url`
+    pub fn build(self) -> Result<Client> {
+        let ClientBuilder { client, api_key, etherscan_api_url, etherscan_url, cache } = self;
+
+        let client = Client {
+            client: client.unwrap_or_default(),
+            api_key: api_key
+                .ok_or_else(|| EtherscanError::Builder("etherscan api key".to_string()))?,
+            etherscan_api_url: etherscan_api_url
+                .ok_or_else(|| EtherscanError::Builder("etherscan api url".to_string()))?,
+            etherscan_url: etherscan_url
+                .ok_or_else(|| EtherscanError::Builder("etherscan url".to_string()))?,
+            cache,
+        };
+        Ok(client)
+    }
+}
+
+/// A wrapper around an Etherscan cache object with an expiry
+#[derive(Clone, Debug, Deserialize, Serialize)]
+struct CacheEnvelope<T> {
+    expiry: u64,
+    data: T,
+}
+
+/// Simple cache for etherscan requests
+#[derive(Clone, Debug)]
+struct Cache {
+    root: PathBuf,
+    ttl: Duration,
+}
+
+impl Cache {
+    fn new(root: PathBuf, ttl: Duration) -> Self {
+        Self { root, ttl }
+    }
+
+    fn get_abi(&self, address: Address) -> Option<Option<ethers_core::abi::Abi>> {
+        self.get("abi", address)
+    }
+
+    fn set_abi(&self, address: Address, abi: Option<&Abi>) {
+        self.set("abi", address, abi)
+    }
+
+    fn get_source(&self, address: Address) -> Option<Option<ContractMetadata>> {
+        self.get("sources", address)
+    }
+
+    fn set_source(&self, address: Address, source: Option<&ContractMetadata>) {
+        self.set("sources", address, source)
+    }
+
+    fn set<T: Serialize>(&self, prefix: &str, address: Address, item: T) {
+        let path = self.root.join(prefix).join(format!("{:?}.json", address));
+        let writer = std::fs::File::create(path).ok().map(std::io::BufWriter::new);
+        if let Some(mut writer) = writer {
+            let _ = serde_json::to_writer(
+                &mut writer,
+                &CacheEnvelope {
+                    expiry: SystemTime::now()
+                        .checked_add(self.ttl)
+                        .expect("cache ttl overflowed")
+                        .duration_since(UNIX_EPOCH)
+                        .expect("system time is before unix epoch")
+                        .as_secs(),
+                    data: item,
+                },
+            );
+            let _ = writer.flush();
+        }
+    }
+
+    fn get<T: DeserializeOwned>(&self, prefix: &str, address: Address) -> Option<T> {
+        let path = self.root.join(prefix).join(format!("{:?}.json", address));
+        let reader = std::io::BufReader::new(std::fs::File::open(path).ok()?);
+        if let Ok(inner) = serde_json::from_reader::<_, CacheEnvelope<T>>(reader) {
+            // If this does not return None then we have passed the expiry
+            if SystemTime::now()
+                .duration_since(UNIX_EPOCH)
+                .expect("system time is before unix epoch")
+                .checked_sub(Duration::from_secs(inner.expiry))
+                .is_some()
+            {
+                return None
+            }
+
+            return Some(inner.data)
+        }
+        None
+    }
+}
+
 /// The API response type
 #[derive(Debug, Clone, Deserialize)]
 pub struct Response<T> {
diff --git a/ethers-middleware/Cargo.toml b/ethers-middleware/Cargo.toml
index 6c0b3167..5cf39a1d 100644
--- a/ethers-middleware/Cargo.toml
+++ b/ethers-middleware/Cargo.toml
@@ -22,7 +22,7 @@ ethers-signers = { version = "^0.6.0", path = "../ethers-signers", default-featu
 
 async-trait = { version = "0.1.50", default-features = false }
 serde = { version = "1.0.124", default-features = false, features = ["derive"] }
-thiserror = { version = "1.0.30", default-features = false }
+thiserror = { version = "1.0.31", default-features = false }
 futures-util = { version = "^0.3" }
 tracing = { version = "0.1.34", default-features = false }
 tracing-futures = { version = "0.2.5", default-features = false }
diff --git a/ethers-providers/Cargo.toml b/ethers-providers/Cargo.toml
index c66c1af9..90f0069b 100644
--- a/ethers-providers/Cargo.toml
+++ b/ethers-providers/Cargo.toml
@@ -21,7 +21,7 @@ hex = { version = "0.4.3", default-features = false, features = ["std"] }
 reqwest = { version = "0.11.10", default-features = false, features = ["json"] }
 serde = { version = "1.0.124", default-features = false, features = ["derive"] }
 serde_json = { version = "1.0.64", default-features = false, features = ["raw_value"] }
-thiserror = { version = "1.0.30", default-features = false }
+thiserror = { version = "1.0.31", default-features = false }
 url = { version = "2.2.2", default-features = false }
 auto_impl = { version = "0.5.0", default-features = false }
 http = { version = "0.2" }
diff --git a/ethers-providers/README.md b/ethers-providers/README.md
index be18a8c7..7e0a38fe 100644
--- a/ethers-providers/README.md
+++ b/ethers-providers/README.md
@@ -10,6 +10,7 @@ For more documentation on the available calls, refer to the
 # Examples
 
 ```no_run
+use ethers_core::types::Address;
 use ethers_providers::{Provider, Http, Middleware};
 use std::convert::TryFrom;
 
@@ -21,7 +22,8 @@ let provider = Provider::<Http>::try_from(
 let block = provider.get_block(100u64).await?;
 println!("Got block: {}", serde_json::to_string(&block)?);
 
-let code = provider.get_code("0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", None).await?;
+let addr = "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359".parse::<Address>()?;
+let code = provider.get_code(addr, None).await?;
 println!("Got code: {}", serde_json::to_string(&code)?);
 # Ok(())
 # }
@@ -29,7 +31,7 @@ println!("Got code: {}", serde_json::to_string(&code)?);
 
 # Websockets
 
-The crate has support for WebSockets via Tokio.
+The crate has support for WebSockets via Tokio. Please ensure that you have the "ws" and "rustls" / "openssl" features enabled if you wish to use WebSockets.
 
 ```
 # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
diff --git a/ethers-signers/Cargo.toml b/ethers-signers/Cargo.toml
index 8714080b..5c157163 100644
--- a/ethers-signers/Cargo.toml
+++ b/ethers-signers/Cargo.toml
@@ -15,7 +15,7 @@ rustdoc-args = ["--cfg", "docsrs"]
 
 [dependencies]
 ethers-core = { version = "^0.6.0", path = "../ethers-core", features = ["eip712"]}
-thiserror = { version = "1.0.30", default-features = false }
+thiserror = { version = "1.0.31", default-features = false }
 coins-bip32 = "0.6.0"
 coins-bip39 = "0.6.0"
 coins-ledger = { version = "0.6.0", default-features = false, optional = true }
@@ -27,12 +27,12 @@ rand = { version = "0.8.5", default-features = false }
 yubihsm = { version = "0.40.0", features = ["secp256k1", "http", "usb"], optional = true }
 futures-util = { version = "^0.3", optional = true }
 futures-executor = { version = "^0.3", optional = true }
-semver = { version =  "1.0.7", optional = true }
+semver = { version =  "1.0.9", optional = true }
 trezor-client = { version = "0.0.5", optional = true, default-features = false, features = ["f_ethereum"] }
 
 # aws
-rusoto_core = { version = "0.47.0", optional = true }
-rusoto_kms = { version = "0.47.0", optional = true }
+rusoto_core = { version = "0.48.0", optional = true }
+rusoto_kms = { version = "0.48.0", optional = true }
 tracing = { version = "0.1.34", optional = true }
 spki = { version = "0.5.4", optional = true }
 
@@ -58,4 +58,4 @@ celo = ["ethers-core/celo"]
 ledger = ["coins-ledger", "futures", "semver"]
 yubi = ["yubihsm"]
 aws = ["rusoto_core", "rusoto_kms", "tracing", "spki"]
-trezor = ["trezor-client", "futures", "semver", "home"]
\ No newline at end of file
+trezor = ["trezor-client", "futures", "semver", "home"]
diff --git a/ethers-signers/src/aws/mod.rs b/ethers-signers/src/aws/mod.rs
index 798cb2db..784f59d6 100644
--- a/ethers-signers/src/aws/mod.rs
+++ b/ethers-signers/src/aws/mod.rs
@@ -203,15 +203,19 @@ impl<'a> AwsSigner<'a> {
     }
 
     /// Sign a digest with this signer's key and add the eip155 `v` value
-    /// corresponding to this signer's chain_id
+    /// corresponding to the input chain_id
     #[instrument(err, skip(digest), fields(digest = %hex::encode(&digest)))]
-    async fn sign_digest_with_eip155(&self, digest: H256) -> Result<EthSig, AwsSignerError> {
+    async fn sign_digest_with_eip155(
+        &self,
+        digest: H256,
+        chain_id: u64,
+    ) -> Result<EthSig, AwsSignerError> {
         let sig = self.sign_digest(digest.into()).await?;
 
         let sig = utils::rsig_from_digest_bytes_trial_recovery(&sig, digest.into(), &self.pubkey);
 
         let mut sig = rsig_to_ethsig(&sig);
-        apply_eip155(&mut sig, self.chain_id);
+        apply_eip155(&mut sig, chain_id);
         Ok(sig)
     }
 }
@@ -230,13 +234,17 @@ impl<'a> super::Signer for AwsSigner<'a> {
         trace!("{:?}", message_hash);
         trace!("{:?}", message);
 
-        self.sign_digest_with_eip155(message_hash).await
+        self.sign_digest_with_eip155(message_hash, self.chain_id).await
     }
 
     #[instrument(err)]
     async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<EthSig, Self::Error> {
+        let mut tx_with_chain = tx.clone();
+        let chain_id = tx_with_chain.chain_id().map(|id| id.as_u64()).unwrap_or(self.chain_id);
+        tx_with_chain.set_chain_id(chain_id);
+
         let sighash = tx.sighash();
-        self.sign_digest_with_eip155(sighash).await
+        self.sign_digest_with_eip155(sighash, chain_id).await
     }
 
     async fn sign_typed_data<T: Eip712 + Send + Sync>(
@@ -245,7 +253,7 @@ impl<'a> super::Signer for AwsSigner<'a> {
     ) -> Result<EthSig, Self::Error> {
         let hash = payload.encode_eip712().map_err(|e| Self::Error::Eip712Error(e.to_string()))?;
 
-        let digest = self.sign_digest_with_eip155(hash.into()).await?;
+        let digest = self.sign_digest_with_eip155(hash.into(), self.chain_id).await?;
 
         Ok(digest)
     }
diff --git a/ethers-signers/src/ledger/app.rs b/ethers-signers/src/ledger/app.rs
index 346231fc..59d7bef1 100644
--- a/ethers-signers/src/ledger/app.rs
+++ b/ethers-signers/src/ledger/app.rs
@@ -117,9 +117,41 @@ impl LedgerEthereum {
 
     /// Signs an Ethereum transaction (requires confirmation on the ledger)
     pub async fn sign_tx(&self, tx: &TypedTransaction) -> Result<Signature, LedgerError> {
+        let mut tx_with_chain = tx.clone();
+        if tx_with_chain.chain_id().is_none() {
+            // in the case we don't have a chain_id, let's use the signer chain id instead
+            tx_with_chain.set_chain_id(self.chain_id);
+        }
         let mut payload = Self::path_to_bytes(&self.derivation);
-        payload.extend_from_slice(tx.rlp().as_ref());
-        self.sign_payload(INS::SIGN, payload).await
+        payload.extend_from_slice(tx_with_chain.rlp().as_ref());
+
+        let mut signature = self.sign_payload(INS::SIGN, payload).await?;
+
+        // modify `v` value of signature to match EIP-155 for chains with large chain ID
+        // The logic is derived from Ledger's library
+        // https://github.com/LedgerHQ/ledgerjs/blob/e78aac4327e78301b82ba58d63a72476ecb842fc/packages/hw-app-eth/src/Eth.ts#L300
+        let eip155_chain_id = self.chain_id * 2 + 35;
+        if eip155_chain_id + 1 > 255 {
+            let one_byte_chain_id = eip155_chain_id % 256;
+            let ecc_parity = if signature.v > one_byte_chain_id {
+                signature.v - one_byte_chain_id
+            } else {
+                one_byte_chain_id - signature.v
+            };
+
+            signature.v = match tx {
+                TypedTransaction::Eip2930(_) | TypedTransaction::Eip1559(_) => {
+                    if ecc_parity % 2 == 1 {
+                        0
+                    } else {
+                        1
+                    }
+                }
+                TypedTransaction::Legacy(_) => eip155_chain_id + ecc_parity,
+            };
+        }
+
+        Ok(signature)
     }
 
     /// Signs an ethereum personal message
diff --git a/ethers-signers/src/ledger/mod.rs b/ethers-signers/src/ledger/mod.rs
index 9f24e13b..817ab1ec 100644
--- a/ethers-signers/src/ledger/mod.rs
+++ b/ethers-signers/src/ledger/mod.rs
@@ -25,7 +25,12 @@ impl Signer for LedgerEthereum {
 
     /// Signs the transaction
     async fn sign_transaction(&self, message: &TypedTransaction) -> Result<Signature, Self::Error> {
-        self.sign_tx(message).await
+        let mut tx_with_chain = message.clone();
+        if tx_with_chain.chain_id().is_none() {
+            // in the case we don't have a chain_id, let's use the signer chain id instead
+            tx_with_chain.set_chain_id(self.chain_id);
+        }
+        self.sign_tx(&tx_with_chain).await
     }
 
     /// Signs a EIP712 derived struct
diff --git a/ethers-signers/src/trezor/app.rs b/ethers-signers/src/trezor/app.rs
index d67d6b22..5994c79a 100644
--- a/ethers-signers/src/trezor/app.rs
+++ b/ethers-signers/src/trezor/app.rs
@@ -160,6 +160,8 @@ impl TrezorEthereum {
 
         let transaction = TrezorTransaction::load(tx)?;
 
+        let chain_id = tx.chain_id().map(|id| id.as_u64()).unwrap_or(self.chain_id);
+
         let signature = match tx {
             TypedTransaction::Eip2930(_) | TypedTransaction::Legacy(_) => client.ethereum_sign_tx(
                 arr_path,
@@ -169,7 +171,7 @@ impl TrezorEthereum {
                 transaction.to,
                 transaction.value,
                 transaction.data,
-                self.chain_id,
+                chain_id,
             )?,
             TypedTransaction::Eip1559(eip1559_tx) => client.ethereum_sign_eip1559_tx(
                 arr_path,
@@ -178,7 +180,7 @@ impl TrezorEthereum {
                 transaction.to,
                 transaction.value,
                 transaction.data,
-                self.chain_id,
+                chain_id,
                 transaction.max_fee_per_gas,
                 transaction.max_priority_fee_per_gas,
                 transaction.access_list,
diff --git a/ethers-signers/src/trezor/mod.rs b/ethers-signers/src/trezor/mod.rs
index 060959b6..4bf0e5b5 100644
--- a/ethers-signers/src/trezor/mod.rs
+++ b/ethers-signers/src/trezor/mod.rs
@@ -25,7 +25,12 @@ impl Signer for TrezorEthereum {
 
     /// Signs the transaction
     async fn sign_transaction(&self, message: &TypedTransaction) -> Result<Signature, Self::Error> {
-        self.sign_tx(message).await
+        let mut tx_with_chain = message.clone();
+        if tx_with_chain.chain_id().is_none() {
+            // in the case we don't have a chain_id, let's use the signer chain id instead
+            tx_with_chain.set_chain_id(self.chain_id);
+        }
+        self.sign_tx(&tx_with_chain).await
     }
 
     /// Signs a EIP712 derived struct
diff --git a/ethers-signers/src/wallet/mod.rs b/ethers-signers/src/wallet/mod.rs
index efde9cf2..5f44ef6f 100644
--- a/ethers-signers/src/wallet/mod.rs
+++ b/ethers-signers/src/wallet/mod.rs
@@ -18,7 +18,7 @@ use ethers_core::{
     },
     types::{
         transaction::{eip2718::TypedTransaction, eip712::Eip712},
-        Address, Signature, H256, U256, U64,
+        Address, Signature, H256, U256,
     },
     utils::hash_message,
 };
@@ -79,27 +79,16 @@ impl<D: Sync + Send + DigestSigner<Sha256Proxy, RecoverableSignature>> Signer fo
         let message = message.as_ref();
         let message_hash = hash_message(message);
 
-        Ok(self.sign_hash(message_hash, false))
+        Ok(self.sign_hash(message_hash))
     }
 
     async fn sign_transaction(&self, tx: &TypedTransaction) -> Result<Signature, Self::Error> {
-        let chain_id = tx.chain_id();
-        match chain_id {
-            Some(id) => {
-                if U64::from(self.chain_id) != id {
-                    return Err(WalletError::InvalidTransactionError(
-                        "transaction chain_id does not match the signer".to_string(),
-                    ))
-                }
-                Ok(self.sign_transaction_sync(tx))
-            }
-            None => {
-                // in the case we don't have a chain_id, let's use the signer chain id instead
-                let mut tx_with_chain = tx.clone();
-                tx_with_chain.set_chain_id(self.chain_id);
-                Ok(self.sign_transaction_sync(&tx_with_chain))
-            }
+        let mut tx_with_chain = tx.clone();
+        if tx_with_chain.chain_id() == None {
+            // in the case we don't have a chain_id, let's use the signer chain id instead
+            tx_with_chain.set_chain_id(self.chain_id);
         }
+        Ok(self.sign_transaction_sync(&tx_with_chain))
     }
 
     async fn sign_typed_data<T: Eip712 + Send + Sync>(
@@ -109,7 +98,7 @@ impl<D: Sync + Send + DigestSigner<Sha256Proxy, RecoverableSignature>> Signer fo
         let encoded =
             payload.encode_eip712().map_err(|e| Self::Error::Eip712Error(e.to_string()))?;
 
-        Ok(self.sign_hash(H256::from(encoded), false))
+        Ok(self.sign_hash(H256::from(encoded)))
     }
 
     fn address(&self) -> Address {
@@ -129,23 +118,24 @@ impl<D: Sync + Send + DigestSigner<Sha256Proxy, RecoverableSignature>> Signer fo
 }
 
 impl<D: DigestSigner<Sha256Proxy, RecoverableSignature>> Wallet<D> {
-    /// Synchronously signs the provided transaction.
+    /// Synchronously signs the provided transaction, normalizing the signature `v` value with
+    /// EIP-155 using the transaction's `chain_id`.
     pub fn sign_transaction_sync(&self, tx: &TypedTransaction) -> Signature {
         let sighash = tx.sighash();
-        self.sign_hash(sighash, true)
+        let chain_id = tx.chain_id().map(|id| id.as_u64()).unwrap_or(self.chain_id);
+        let mut sig = self.sign_hash(sighash);
+
+        // sign_hash sets `v` to recid + 27, so we need to subtract 27 before normalizing
+        sig.v = to_eip155_v(sig.v as u8 - 27, chain_id);
+        sig
     }
 
-    /// Signs the provided hash and proceeds to normalize the `v` value of the
-    /// signature with EIP-155 if the flag is set to true.
-    pub fn sign_hash(&self, hash: H256, eip155: bool) -> Signature {
+    /// Signs the provided hash.
+    pub fn sign_hash(&self, hash: H256) -> Signature {
         let recoverable_sig: RecoverableSignature =
             self.signer.sign_digest(Sha256Proxy::from(hash));
 
-        let v = if eip155 {
-            to_eip155_v(recoverable_sig.recovery_id(), self.chain_id)
-        } else {
-            u8::from(recoverable_sig.recovery_id()) as u64 + 27
-        };
+        let v = u8::from(recoverable_sig.recovery_id()) as u64 + 27;
 
         let r_bytes: FieldBytes<Secp256k1> = recoverable_sig.r().into();
         let s_bytes: FieldBytes<Secp256k1> = recoverable_sig.s().into();
diff --git a/ethers-signers/src/wallet/private_key.rs b/ethers-signers/src/wallet/private_key.rs
index eb3f2cfb..d2bd75fa 100644
--- a/ethers-signers/src/wallet/private_key.rs
+++ b/ethers-signers/src/wallet/private_key.rs
@@ -46,8 +46,6 @@ pub enum WalletError {
     /// Error type from Eip712Error message
     #[error("error encoding eip712 struct: {0:?}")]
     Eip712Error(String),
-    #[error("invalid transaction: {0:?}")]
-    InvalidTransactionError(String),
 }
 
 impl Clone for Wallet<SigningKey> {
@@ -221,6 +219,39 @@ mod tests {
         assert!(sig.verify(sighash, wallet.address).is_ok());
     }
 
+    #[tokio::test]
+    #[cfg(not(feature = "celo"))]
+    async fn signs_tx_empty_chain_id() {
+        use crate::TypedTransaction;
+        use ethers_core::types::TransactionRequest;
+        // retrieved test vector from:
+        // https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
+        let tx: TypedTransaction = TransactionRequest {
+            from: None,
+            to: Some("F0109fC8DF283027b6285cc889F5aA624EaC1F55".parse::<Address>().unwrap().into()),
+            value: Some(1_000_000_000.into()),
+            gas: Some(2_000_000.into()),
+            nonce: Some(0.into()),
+            gas_price: Some(21_000_000_000u128.into()),
+            data: None,
+            chain_id: None,
+        }
+        .into();
+        let wallet: Wallet<SigningKey> =
+            "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318".parse().unwrap();
+        let wallet = wallet.with_chain_id(1u64);
+
+        // this should populate the tx chain_id as the signer's chain_id (1) before signing
+        let sig = wallet.sign_transaction(&tx).await.unwrap();
+
+        // since we initialize with None we need to re-set the chain_id for the sighash to be
+        // correct
+        let mut tx = tx;
+        tx.set_chain_id(1);
+        let sighash = tx.sighash();
+        assert!(sig.verify(sighash, wallet.address).is_ok());
+    }
+
     #[test]
     fn key_to_address() {
         let wallet: Wallet<SigningKey> =
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index 3a9d53cd..0fdf5c43 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -17,14 +17,14 @@ keywords = ["ethereum", "web3", "solc", "solidity", "ethers"]
 ethers-core = { version = "^0.6.0", path = "../ethers-core", default-features = false }
 serde_json = "1.0.68"
 serde = { version = "1.0.130", features = ["derive"] }
-semver = { version = "1.0.7", features = ["serde"] }
+semver = { version = "1.0.9", features = ["serde"] }
 walkdir = "2.3.2"
 tokio = { version = "1.15.0", default-features = false, features = ["process", "io-util", "fs", "time"], optional = true }
 futures-util = { version = "^0.3", optional = true }
 once_cell = "1.10.0"
 regex = "1.5.5"
 md-5 = "0.10.1"
-thiserror = "1.0.30"
+thiserror = "1.0.31"
 hex = "0.4.3"
 colored = "2.0.0"
 glob = "0.3.0"
diff --git a/ethers-solc/src/compile/project.rs b/ethers-solc/src/compile/project.rs
index 2d08c7f7..0a0595c4 100644
--- a/ethers-solc/src/compile/project.rs
+++ b/ethers-solc/src/compile/project.rs
@@ -543,11 +543,20 @@ fn compile_parallel(
         }
     }
 
+    // need to get the currently installed reporter before installing the pool, otherwise each new
+    // thread in the pool will get initialized with the default value of the `thread_local!`'s
+    // localkey. This way we keep access to the reporter in the rayon pool
+    let scoped_report = report::get_default(|reporter| reporter.clone());
+
     // start a rayon threadpool that will execute all `Solc::compile()` processes
     let pool = rayon::ThreadPoolBuilder::new().num_threads(num_jobs).build().unwrap();
+
     let outputs = pool.install(move || {
         jobs.into_par_iter()
-            .map(|(solc, version, input, actually_dirty)| {
+            .map(move |(solc, version, input, actually_dirty)| {
+                // set the reporter on this thread
+                let _guard = report::set_scoped(&scoped_report);
+
                 tracing::trace!(
                     "calling solc `{}` {:?} with {} sources: {:?}",
                     version,
diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs
index 261b5b38..90307818 100644
--- a/ethers-solc/src/config.rs
+++ b/ethers-solc/src/config.rs
@@ -3,7 +3,7 @@ use crate::{
     cache::SOLIDITY_FILES_CACHE_FILENAME,
     error::{Result, SolcError, SolcIoError},
     remappings::Remapping,
-    resolver::Graph,
+    resolver::{Graph, SolImportAlias},
     utils, Source, Sources,
 };
 
@@ -281,43 +281,69 @@ impl ProjectPathsConfig {
         let target_node = graph.node(*target_index);
 
         let mut imports = target_node.imports().clone();
-        imports.sort_by_key(|x| x.loc().0);
+        imports.sort_by_key(|x| x.loc().start);
+
+        let mut content = target_node.content().to_owned();
+
+        for alias in imports.iter().flat_map(|i| i.data().aliases()) {
+            let (alias, target) = match alias {
+                SolImportAlias::Contract(alias, target) => (alias.clone(), target.clone()),
+                _ => continue,
+            };
+            let name_regex = utils::create_contract_or_lib_name_regex(&alias);
+            let target_len = target.len() as isize;
+            let mut replace_offset = 0;
+            for cap in name_regex.captures_iter(&content.clone()) {
+                if cap.name("ignore").is_some() {
+                    continue
+                }
+                if let Some(name_match) =
+                    vec!["n1", "n2", "n3"].iter().find_map(|name| cap.name(name))
+                {
+                    let name_match_range =
+                        utils::range_by_offset(&name_match.range(), replace_offset);
+                    replace_offset += target_len - (name_match_range.len() as isize);
+                    content.replace_range(name_match_range, &target);
+                }
+            }
+        }
 
-        let mut content = target_node.content().as_bytes().to_vec();
+        let mut content = content.as_bytes().to_vec();
         let mut offset = 0_isize;
 
         if strip_license {
             if let Some(license) = target_node.license() {
-                let (start, end) = license.loc_by_offset(offset);
-                content.splice(start..end, std::iter::empty());
-                offset -= (end - start) as isize;
+                let license_range = license.loc_by_offset(offset);
+                offset -= license_range.len() as isize;
+                content.splice(license_range, std::iter::empty());
             }
         }
 
         if strip_version_pragma {
             if let Some(version) = target_node.version() {
-                let (start, end) = version.loc_by_offset(offset);
-                content.splice(start..end, std::iter::empty());
-                offset -= (end - start) as isize;
+                let version_range = version.loc_by_offset(offset);
+                offset -= version_range.len() as isize;
+                content.splice(version_range, std::iter::empty());
             }
         }
 
         if strip_experimental_pragma {
             if let Some(experiment) = target_node.experimental() {
-                let (start, end) = experiment.loc_by_offset(offset);
-                content.splice(start..end, std::iter::empty());
-                offset -= (end - start) as isize;
+                let experimental_pragma_range = experiment.loc_by_offset(offset);
+                offset -= experimental_pragma_range.len() as isize;
+                content.splice(experimental_pragma_range, std::iter::empty());
             }
         }
 
         for import in imports.iter() {
-            let import_path = self.resolve_import(target_dir, import.data())?;
+            let import_path = self.resolve_import(target_dir, import.data().path())?;
             let s = self.flatten_node(&import_path, graph, imported, true, true, true)?;
+
             let import_content = s.as_bytes();
             let import_content_len = import_content.len() as isize;
-            let (start, end) = import.loc_by_offset(offset);
-            content.splice(start..end, import_content.iter().copied());
-            offset += import_content_len - ((end - start) as isize);
+            let import_range = import.loc_by_offset(offset);
+            offset += import_content_len - (import_range.len() as isize);
+            content.splice(import_range, import_content.iter().copied());
         }
 
         let result = String::from_utf8(content).map_err(|err| {
diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs
index c2fcd705..69ab0e6d 100644
--- a/ethers-solc/src/lib.rs
+++ b/ethers-solc/src/lib.rs
@@ -3,7 +3,7 @@ pub mod artifacts;
 pub mod sourcemap;
 
 pub use artifacts::{CompilerInput, CompilerOutput, EvmVersion};
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, HashSet};
 
 mod artifact_output;
 pub mod cache;
@@ -440,11 +440,17 @@ impl<T: ArtifactOutput> Project<T> {
         let target_index = graph.files().get(target).ok_or_else(|| {
             SolcError::msg(format!("cannot resolve file at {:?}", target.display()))
         })?;
+
         let mut sources = Vec::new();
+        let mut unique_paths = HashSet::new();
         let (path, source) = graph.node(*target_index).unpack();
+        unique_paths.insert(path.clone());
         sources.push((path, source));
         sources.extend(
-            graph.all_imported_nodes(*target_index).map(|index| graph.node(index).unpack()),
+            graph
+                .all_imported_nodes(*target_index)
+                .map(|index| graph.node(index).unpack())
+                .filter(|(p, _)| unique_paths.insert(p.to_path_buf())),
         );
 
         let root = self.root();
diff --git a/ethers-solc/src/report/compiler.rs b/ethers-solc/src/report/compiler.rs
index b77f1b65..b1e74edd 100644
--- a/ethers-solc/src/report/compiler.rs
+++ b/ethers-solc/src/report/compiler.rs
@@ -6,6 +6,7 @@
 //! separate json file
 
 use crate::{CompilerInput, CompilerOutput};
+use semver::Version;
 use std::{env, path::PathBuf, str::FromStr};
 
 /// Debug Helper type that can be used to write the [crate::Solc] [CompilerInput] and
@@ -14,7 +15,8 @@ use std::{env, path::PathBuf, str::FromStr};
 /// # Example
 ///
 /// If `ETHERS_SOLC_LOG=in=in.json,out=out.json` is then the reporter will be configured to write
-/// the compiler input as pretty formatted json to `in.json` and the compiler output to `out.json`
+/// the compiler input as pretty formatted json to `in.{solc version}.json` and the compiler output
+/// to `out.{solc version}.json`
 ///
 /// ```no_run
 /// use ethers_solc::report::SolcCompilerIoReporter;
@@ -53,16 +55,16 @@ impl SolcCompilerIoReporter {
     }
 
     /// Callback to write the input to disk if target is set
-    pub fn log_compiler_input(&self, input: &CompilerInput) {
+    pub fn log_compiler_input(&self, input: &CompilerInput, version: &Version) {
         if let Some(ref target) = self.target {
-            target.write_input(input)
+            target.write_input(input, version)
         }
     }
 
     /// Callback to write the input to disk if target is set
-    pub fn log_compiler_output(&self, output: &CompilerOutput) {
+    pub fn log_compiler_output(&self, output: &CompilerOutput, version: &Version) {
         if let Some(ref target) = self.target {
-            target.write_output(output)
+            target.write_output(output, version)
         }
     }
 }
@@ -86,11 +88,11 @@ struct Target {
 }
 
 impl Target {
-    fn write_input(&self, input: &CompilerInput) {
+    fn write_input(&self, input: &CompilerInput, version: &Version) {
         tracing::trace!("logging compiler input to {}", self.dest_input.display());
         match serde_json::to_string_pretty(input) {
             Ok(json) => {
-                if let Err(err) = std::fs::write(&self.dest_input, json) {
+                if let Err(err) = std::fs::write(get_file_name(&self.dest_input, version), json) {
                     tracing::error!("Failed to write compiler input: {}", err)
                 }
             }
@@ -100,11 +102,11 @@ impl Target {
         }
     }
 
-    fn write_output(&self, output: &CompilerOutput) {
+    fn write_output(&self, output: &CompilerOutput, version: &Version) {
         tracing::trace!("logging compiler output to {}", self.dest_output.display());
         match serde_json::to_string_pretty(output) {
             Ok(json) => {
-                if let Err(err) = std::fs::write(&self.dest_output, json) {
+                if let Err(err) = std::fs::write(get_file_name(&self.dest_output, version), json) {
                     tracing::error!("Failed to write compiler output: {}", err)
                 }
             }
@@ -157,10 +159,30 @@ pub struct BadName {
     name: String,
 }
 
+/// Returns the file name for the given version
+fn get_file_name(path: impl Into<PathBuf>, v: &Version) -> PathBuf {
+    let mut path = path.into();
+    if let Some(stem) = path.file_stem().and_then(|s| s.to_str().map(|s| s.to_string())) {
+        path.set_file_name(format!("{}.{}.{}.{}.json", stem, v.major, v.minor, v.patch));
+    }
+    path
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
 
+    #[test]
+    fn can_set_file_name() {
+        let s = "/a/b/c/in.json";
+        let p = get_file_name(s, &Version::parse("0.8.10").unwrap());
+        assert_eq!(PathBuf::from("/a/b/c/in.0.8.10.json"), p);
+
+        let s = "abc.json";
+        let p = get_file_name(s, &Version::parse("0.8.10").unwrap());
+        assert_eq!(PathBuf::from("abc.0.8.10.json"), p);
+    }
+
     #[test]
     fn can_parse_target() {
         let target: Target = "in=in.json,out=out.json".parse().unwrap();
diff --git a/ethers-solc/src/report/mod.rs b/ethers-solc/src/report/mod.rs
index fd549aab..e87a586c 100644
--- a/ethers-solc/src/report/mod.rs
+++ b/ethers-solc/src/report/mod.rs
@@ -93,7 +93,7 @@ where
 /// print custom messages to `stdout`.
 ///
 /// A `Reporter` is entirely passive and only listens to incoming "events".
-pub trait Reporter: 'static {
+pub trait Reporter: 'static + std::fmt::Debug {
     /// Callback invoked right before [`Solc::compile()`] is called
     ///
     /// This contains the [Solc] its [Version] the complete [CompilerInput] and all files that
@@ -453,6 +453,7 @@ mod tests {
 
     #[test]
     fn scoped_reporter_works() {
+        #[derive(Debug)]
         struct TestReporter;
         impl Reporter for TestReporter {}
 
@@ -468,6 +469,7 @@ mod tests {
         });
 
         set_global_reporter(Report::new(BasicStdoutReporter::default())).unwrap();
+        #[derive(Debug)]
         struct TestReporter;
         impl Reporter for TestReporter {}
 
diff --git a/ethers-solc/src/resolver/mod.rs b/ethers-solc/src/resolver/mod.rs
index b4c6ed7f..9183fd0b 100644
--- a/ethers-solc/src/resolver/mod.rs
+++ b/ethers-solc/src/resolver/mod.rs
@@ -52,7 +52,7 @@ use std::{
     path::{Path, PathBuf},
 };
 
-use parse::{SolData, SolDataUnit};
+use parse::{SolData, SolDataUnit, SolImport};
 use rayon::prelude::*;
 
 use semver::VersionReq;
@@ -62,6 +62,7 @@ use crate::{error::Result, utils, ProjectPathsConfig, SolcError, Source, Sources
 mod parse;
 mod tree;
 
+pub use parse::SolImportAlias;
 pub use tree::{print, Charset, TreeOptions};
 
 /// The underlying edges of the graph which only contains the raw relationship data.
@@ -339,13 +340,14 @@ impl Graph {
             };
 
             for import in node.data.imports.iter() {
-                match paths.resolve_import(cwd, import.data()) {
+                let import_path = import.data().path();
+                match paths.resolve_import(cwd, import_path) {
                     Ok(import) => {
                         add_node(&mut unresolved, &mut index, &mut resolved_imports, import)?;
                     }
                     Err(err) => {
-                        if unresolved_paths.insert(import.data().to_path_buf()) {
-                            crate::report::unresolved_import(import.data(), &paths.remappings);
+                        if unresolved_paths.insert(import_path.to_path_buf()) {
+                            crate::report::unresolved_import(import_path, &paths.remappings);
                         }
                         tracing::trace!(
                             "failed to resolve import component \"{:?}\" for {:?}",
@@ -772,7 +774,7 @@ impl Node {
         &self.source.content
     }
 
-    pub fn imports(&self) -> &Vec<SolDataUnit<PathBuf>> {
+    pub fn imports(&self) -> &Vec<SolDataUnit<SolImport>> {
         &self.data.imports
     }
 
@@ -854,7 +856,7 @@ mod tests {
         let dapp_test = graph.node(1);
         assert_eq!(dapp_test.path, paths.sources.join("Dapp.t.sol"));
         assert_eq!(
-            dapp_test.data.imports.iter().map(|i| i.data()).collect::<Vec<&PathBuf>>(),
+            dapp_test.data.imports.iter().map(|i| i.data().path()).collect::<Vec<&PathBuf>>(),
             vec![&PathBuf::from("ds-test/test.sol"), &PathBuf::from("./Dapp.sol")]
         );
         assert_eq!(graph.imported_nodes(1).to_vec(), vec![2, 0]);
diff --git a/ethers-solc/src/resolver/parse.rs b/ethers-solc/src/resolver/parse.rs
index 028856f9..4bcd8807 100644
--- a/ethers-solc/src/resolver/parse.rs
+++ b/ethers-solc/src/resolver/parse.rs
@@ -1,11 +1,13 @@
 use crate::{utils, Solc};
-use regex::Match;
 use semver::VersionReq;
 use solang_parser::pt::{
     ContractPart, ContractTy, FunctionAttribute, FunctionDefinition, Import, Loc, SourceUnitPart,
     Visibility,
 };
-use std::path::{Path, PathBuf};
+use std::{
+    ops::Range,
+    path::{Path, PathBuf},
+};
 
 /// Represents various information about a solidity file parsed via [solang_parser]
 #[derive(Debug)]
@@ -14,7 +16,7 @@ pub struct SolData {
     pub license: Option<SolDataUnit<String>>,
     pub version: Option<SolDataUnit<String>>,
     pub experimental: Option<SolDataUnit<String>>,
-    pub imports: Vec<SolDataUnit<PathBuf>>,
+    pub imports: Vec<SolDataUnit<SolImport>>,
     pub version_req: Option<VersionReq>,
     pub libraries: Vec<SolLibrary>,
     pub contracts: Vec<SolContract>,
@@ -39,7 +41,7 @@ impl SolData {
     pub fn parse(content: &str, file: &Path) -> Self {
         let mut version = None;
         let mut experimental = None;
-        let mut imports = Vec::<SolDataUnit<PathBuf>>::new();
+        let mut imports = Vec::<SolDataUnit<SolImport>>::new();
         let mut libraries = Vec::new();
         let mut contracts = Vec::new();
 
@@ -50,22 +52,30 @@ impl SolData {
                         SourceUnitPart::PragmaDirective(loc, _, pragma, value) => {
                             if pragma.name == "solidity" {
                                 // we're only interested in the solidity version pragma
-                                version = Some(SolDataUnit::new(value.string.clone(), loc.into()));
+                                version = Some(SolDataUnit::from_loc(value.string.clone(), loc));
                             }
 
                             if pragma.name == "experimental" {
                                 experimental =
-                                    Some(SolDataUnit::new(value.string.clone(), loc.into()));
+                                    Some(SolDataUnit::from_loc(value.string.clone(), loc));
                             }
                         }
                         SourceUnitPart::ImportDirective(_, import) => {
-                            let (import, loc) = match import {
-                                Import::Plain(s, l) => (s, l),
-                                Import::GlobalSymbol(s, _, l) => (s, l),
-                                Import::Rename(s, _, l) => (s, l),
+                            let (import, ids, loc) = match import {
+                                Import::Plain(s, l) => (s, vec![], l),
+                                Import::GlobalSymbol(s, i, l) => (s, vec![(i, None)], l),
+                                Import::Rename(s, i, l) => (s, i, l),
                             };
-                            imports
-                                .push(SolDataUnit::new(PathBuf::from(import.string), loc.into()));
+                            let sol_import = SolImport::new(PathBuf::from(import.string))
+                                .set_aliases(
+                                    ids.into_iter()
+                                        .map(|(id, alias)| match alias {
+                                            Some(al) => SolImportAlias::Contract(al.name, id.name),
+                                            None => SolImportAlias::File(id.name),
+                                        })
+                                        .collect(),
+                                );
+                            imports.push(SolDataUnit::from_loc(sol_import, loc));
                         }
                         SourceUnitPart::ContractDefinition(def) => {
                             let functions = def
@@ -100,16 +110,14 @@ impl SolData {
                 version =
                     capture_outer_and_inner(content, &utils::RE_SOL_PRAGMA_VERSION, &["version"])
                         .first()
-                        .map(|(cap, name)| {
-                            SolDataUnit::new(name.as_str().to_owned(), cap.to_owned().into())
-                        });
+                        .map(|(cap, name)| SolDataUnit::new(name.as_str().to_owned(), cap.range()));
                 imports = capture_imports(content);
             }
         };
         let license = content.lines().next().and_then(|line| {
             capture_outer_and_inner(line, &utils::RE_SOL_SDPX_LICENSE_IDENTIFIER, &["license"])
                 .first()
-                .map(|(cap, l)| SolDataUnit::new(l.as_str().to_owned(), cap.to_owned().into()))
+                .map(|(cap, l)| SolDataUnit::new(l.as_str().to_owned(), cap.range()))
         });
         let version_req = version.as_ref().and_then(|v| Solc::version_req(v.data()).ok());
 
@@ -130,6 +138,37 @@ pub struct SolContract {
     pub functions: Vec<FunctionDefinition>,
 }
 
+#[derive(Debug, Clone)]
+pub struct SolImport {
+    path: PathBuf,
+    aliases: Vec<SolImportAlias>,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum SolImportAlias {
+    File(String),
+    Contract(String, String),
+}
+
+impl SolImport {
+    pub fn new(path: PathBuf) -> Self {
+        Self { path, aliases: vec![] }
+    }
+
+    pub fn path(&self) -> &PathBuf {
+        &self.path
+    }
+
+    pub fn aliases(&self) -> &Vec<SolImportAlias> {
+        &self.aliases
+    }
+
+    fn set_aliases(mut self, aliases: Vec<SolImportAlias>) -> Self {
+        self.aliases = aliases;
+        self
+    }
+}
+
 /// Minimal representation of a contract inside a solidity file
 #[derive(Debug)]
 pub struct SolLibrary {
@@ -164,57 +203,41 @@ impl SolLibrary {
 /// Represents an item in a solidity file with its location in the file
 #[derive(Debug, Clone)]
 pub struct SolDataUnit<T> {
-    loc: Location,
+    loc: Range<usize>,
     data: T,
 }
 
-/// Location in a text file buffer
-#[derive(Debug, Clone)]
-pub struct Location {
-    pub start: usize,
-    pub end: usize,
-}
-
 /// Solidity Data Unit decorated with its location within the file
 impl<T> SolDataUnit<T> {
-    pub fn new(data: T, loc: Location) -> Self {
+    pub fn new(data: T, loc: Range<usize>) -> Self {
         Self { data, loc }
     }
 
+    pub fn from_loc(data: T, loc: Loc) -> Self {
+        Self {
+            data,
+            loc: match loc {
+                Loc::File(_, start, end) => Range { start, end: end + 1 },
+                _ => Range { start: 0, end: 0 },
+            },
+        }
+    }
+
     /// Returns the underlying data for the unit
     pub fn data(&self) -> &T {
         &self.data
     }
 
     /// Returns the location of the given data unit
-    pub fn loc(&self) -> (usize, usize) {
-        (self.loc.start, self.loc.end)
+    pub fn loc(&self) -> Range<usize> {
+        self.loc.clone()
     }
 
     /// Returns the location of the given data unit adjusted by an offset.
     /// Used to determine new position of the unit within the file after
     /// content manipulation.
-    pub fn loc_by_offset(&self, offset: isize) -> (usize, usize) {
-        (
-            offset.saturating_add(self.loc.start as isize) as usize,
-            // make the end location exclusive
-            offset.saturating_add(self.loc.end as isize + 1) as usize,
-        )
-    }
-}
-
-impl From<Match<'_>> for Location {
-    fn from(src: Match) -> Self {
-        Location { start: src.start(), end: src.end() }
-    }
-}
-
-impl From<Loc> for Location {
-    fn from(src: Loc) -> Self {
-        match src {
-            Loc::File(_, start, end) => Location { start, end },
-            _ => Location { start: 0, end: 0 },
-        }
+    pub fn loc_by_offset(&self, offset: isize) -> Range<usize> {
+        utils::range_by_offset(&self.loc, offset)
     }
 }
 
@@ -238,12 +261,31 @@ fn capture_outer_and_inner<'a>(
         })
         .collect()
 }
-
-pub fn capture_imports(content: &str) -> Vec<SolDataUnit<PathBuf>> {
-    capture_outer_and_inner(content, &utils::RE_SOL_IMPORT, &["p1", "p2", "p3", "p4"])
-        .iter()
-        .map(|(cap, m)| SolDataUnit::new(PathBuf::from(m.as_str()), cap.to_owned().into()))
-        .collect()
+/// Capture the import statement information together with aliases
+pub fn capture_imports(content: &str) -> Vec<SolDataUnit<SolImport>> {
+    let mut imports = vec![];
+    for cap in utils::RE_SOL_IMPORT.captures_iter(content) {
+        if let Some(name_match) =
+            vec!["p1", "p2", "p3", "p4"].iter().find_map(|name| cap.name(name))
+        {
+            let statement_match = cap.get(0).unwrap();
+            let mut aliases = vec![];
+            for alias_cap in utils::RE_SOL_IMPORT_ALIAS.captures_iter(statement_match.as_str()) {
+                if let Some(alias) = alias_cap.name("alias") {
+                    let alias = alias.as_str().to_owned();
+                    let import_alias = match alias_cap.name("target") {
+                        Some(target) => SolImportAlias::Contract(alias, target.as_str().to_owned()),
+                        None => SolImportAlias::File(alias),
+                    };
+                    aliases.push(import_alias);
+                }
+            }
+            let sol_import =
+                SolImport::new(PathBuf::from(name_match.as_str())).set_aliases(aliases);
+            imports.push(SolDataUnit::new(sol_import, statement_match.range()));
+        }
+    }
+    imports
 }
 
 #[cfg(test)]
@@ -259,7 +301,7 @@ import {DsTest} from "ds-test/test.sol";
 "#;
 
         let captured_imports =
-            capture_imports(content).into_iter().map(|s| s.data).collect::<Vec<_>>();
+            capture_imports(content).into_iter().map(|s| s.data.path).collect::<Vec<_>>();
 
         let expected =
             utils::find_import_paths(content).map(|m| m.as_str().into()).collect::<Vec<PathBuf>>();
@@ -275,4 +317,29 @@ import {DsTest} from "ds-test/test.sol";
             ]
         );
     }
+
+    #[test]
+    fn cap_capture_aliases() {
+        let content = r#"
+import * as T from "./Test.sol";
+import { DsTest as Test } from "ds-test/test.sol";
+import "ds-test/test.sol" as Test;
+import { FloatMath as Math, Math as FloatMath } from "./Math.sol";
+"#;
+
+        let caputred_imports =
+            capture_imports(content).into_iter().map(|s| s.data.aliases).collect::<Vec<_>>();
+        assert_eq!(
+            caputred_imports,
+            vec![
+                vec![SolImportAlias::File("T".into())],
+                vec![SolImportAlias::Contract("Test".into(), "DsTest".into())],
+                vec![SolImportAlias::File("Test".into())],
+                vec![
+                    SolImportAlias::Contract("Math".into(), "FloatMath".into()),
+                    SolImportAlias::Contract("FloatMath".into(), "Math".into()),
+                ],
+            ]
+        );
+    }
 }
diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs
index bd354228..48e2cb59 100644
--- a/ethers-solc/src/utils.rs
+++ b/ethers-solc/src/utils.rs
@@ -2,6 +2,7 @@
 
 use std::{
     collections::HashSet,
+    ops::Range,
     path::{Component, Path, PathBuf},
 };
 
@@ -17,9 +18,13 @@ use walkdir::WalkDir;
 /// statement with the named groups "path", "id".
 // Adapted from <https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L100>
 pub static RE_SOL_IMPORT: Lazy<Regex> = Lazy::new(|| {
-    Regex::new(r#"import\s+(?:(?:"(?P<p1>[^;]*)"|'(?P<p2>[^;]*)')(?:;|\s+as\s+(?P<id>[^;]*);)|.+from\s+(?:"(?P<p3>.*)"|'(?P<p4>.*)');)"#).unwrap()
+    Regex::new(r#"import\s+(?:(?:"(?P<p1>.*)"|'(?P<p2>.*)')(?:\s+as\s+\w+)?|(?:(?:\w+(?:\s+as\s+\w+)?|\*\s+as\s+\w+|\{\s*(?:\w+(?:\s+as\s+\w+)?(?:\s*,\s*)?)+\s*\})\s+from\s+(?:"(?P<p3>.*)"|'(?P<p4>.*)')))\s*;"#).unwrap()
 });
 
+/// A regex that matches an alias within an import statement
+pub static RE_SOL_IMPORT_ALIAS: Lazy<Regex> =
+    Lazy::new(|| Regex::new(r#"(?:(?P<target>\w+)|\*|'|")\s+as\s+(?P<alias>\w+)"#).unwrap());
+
 /// A regex that matches the version part of a solidity pragma
 /// as follows: `pragma solidity ^0.5.2;` => `^0.5.2`
 /// statement with the named group "version".
@@ -35,6 +40,19 @@ pub static RE_SOL_SDPX_LICENSE_IDENTIFIER: Lazy<Regex> =
 /// A regex used to remove extra lines in flatenned files
 pub static RE_THREE_OR_MORE_NEWLINES: Lazy<Regex> = Lazy::new(|| Regex::new("\n{3,}").unwrap());
 
+/// Create a regex that matches any library or contract name inside a file
+pub fn create_contract_or_lib_name_regex(name: &str) -> Regex {
+    Regex::new(&format!(r#"(?:using\s+(?P<n1>{name})\s+|is\s+(?:\w+\s*,\s*)*(?P<n2>{name})(?:\s*,\s*\w+)*|(?:(?P<ignore>(?:function|error|as)\s+|\n[^\n]*(?:"([^"\n]|\\")*|'([^'\n]|\\')*))|\W+)(?P<n3>{name})(?:\.|\(| ))"#, name = name)).unwrap()
+}
+
+/// Move a range by a specified offset
+pub fn range_by_offset(range: &Range<usize>, offset: isize) -> Range<usize> {
+    Range {
+        start: offset.saturating_add(range.start as isize) as usize,
+        end: offset.saturating_add(range.end as isize) as usize,
+    }
+}
+
 /// Returns all path parts from any solidity import statement in a string,
 /// `import "./contracts/Contract.sol";` -> `"./contracts/Contract.sol"`.
 ///
diff --git a/examples/decode_tx_input.rs b/examples/decode_tx_input.rs
index 6c6025ce..c52f1c36 100644
--- a/examples/decode_tx_input.rs
+++ b/examples/decode_tx_input.rs
@@ -14,11 +14,12 @@ fn main() -> Result<()> {
     let tx_input = "0x38ed173900000000000000000000000000000000000000000001a717cc0a3e4f84c00000000000000000000000000000000000000000000000000000000000000283568400000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000201f129111c60401630932d9f9811bd5b5fff34e000000000000000000000000000000000000000000000000000000006227723d000000000000000000000000000000000000000000000000000000000000000200000000000000000000000095ad61b0a150d79219dcf64e1e6cc01f0b64c4ce000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7";
     let calldata: Bytes = tx_input.parse().unwrap();
     let decoded = SwapExactTokensForTokensCall::decode(&calldata)?;
-
-    let from = decoded.path.into_iter().next().unwrap();
+    let mut path = decoded.path.into_iter();
+    let from = path.next().unwrap();
+    let to = path.next().unwrap();
     println!(
         "Swapped {} of token {} for {} of token {}",
-        decoded.amount_in, from, decoded.amount_out_min, decoded.to
+        decoded.amount_in, from, decoded.amount_out_min, to
     );
 
     Ok(())
diff --git a/examples/ethers-wasm/yarn.lock b/examples/ethers-wasm/yarn.lock
index 5d752add..1a32e46d 100644
--- a/examples/ethers-wasm/yarn.lock
+++ b/examples/ethers-wasm/yarn.lock
@@ -362,9 +362,9 @@ async-limiter@~1.0.0:
   integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
 
 async@^2.6.2:
-  version "2.6.3"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
-  integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
+  version "2.6.4"
+  resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
+  integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
   dependencies:
     lodash "^4.17.14"
 


Test patch:
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index e10674c0..40f9aaa4 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -721,6 +721,154 @@ contract A { }
     );
 }
 
+#[test]
+fn can_flatten_with_alias() {
+    let project = TempProject::dapptools().unwrap();
+
+    let f = project
+        .add_source(
+            "Contract",
+            r#"pragma solidity ^0.8.10;
+import { ParentContract as Parent } from "./Parent.sol";
+import { AnotherParentContract as AnotherParent } from "./AnotherParent.sol";
+import { PeerContract as Peer } from "./Peer.sol";
+import { MathLibrary as Math } from "./Math.sol";
+import * as Lib from "./SomeLib.sol";
+
+contract Contract is Parent,
+    AnotherParent {
+    using Math for uint256;
+
+    string public usingString = "using Math for uint256;";
+    string public inheritanceString = "\"Contract is Parent {\"";
+    string public castString = 'Peer(smth) ';
+    string public methodString = '\' Math.max()';
+
+    Peer public peer;
+
+    error Peer();
+
+    constructor(address _peer) {
+        peer = Peer(_peer);
+    }
+
+    function Math(uint256 value) external pure returns (uint256) {
+        return Math.minusOne(Math.max() - value.diffMax());
+    }
+}
+"#,
+        )
+        .unwrap();
+
+    project
+        .add_source(
+            "Parent",
+            r#"pragma solidity ^0.8.10;
+contract ParentContract { }
+"#,
+        )
+        .unwrap();
+
+    project
+        .add_source(
+            "AnotherParent",
+            r#"pragma solidity ^0.8.10;
+contract AnotherParentContract { }
+"#,
+        )
+        .unwrap();
+
+    project
+        .add_source(
+            "Peer",
+            r#"pragma solidity ^0.8.10;
+contract PeerContract { }
+"#,
+        )
+        .unwrap();
+
+    project
+        .add_source(
+            "Math",
+            r#"pragma solidity ^0.8.10;
+library MathLibrary {
+    function minusOne(uint256 val) internal returns (uint256) {
+        return val - 1;
+    }
+
+    function max() internal returns (uint256) {
+        return type(uint256).max;
+    }
+
+    function diffMax(uint256 value) internal returns (uint256) {
+        return type(uint256).max - value;
+    }
+}
+"#,
+        )
+        .unwrap();
+
+    project
+        .add_source(
+            "SomeLib",
+            r#"pragma solidity ^0.8.10;
+library SomeLib { }
+"#,
+        )
+        .unwrap();
+
+    let result = project.flatten(&f).unwrap();
+    assert_eq!(
+        result,
+        r#"pragma solidity ^0.8.10;
+
+contract ParentContract { }
+
+contract AnotherParentContract { }
+
+contract PeerContract { }
+
+library MathLibrary {
+    function minusOne(uint256 val) internal returns (uint256) {
+        return val - 1;
+    }
+
+    function max() internal returns (uint256) {
+        return type(uint256).max;
+    }
+
+    function diffMax(uint256 value) internal returns (uint256) {
+        return type(uint256).max - value;
+    }
+}
+
+library SomeLib { }
+
+contract Contract is ParentContract,
+    AnotherParentContract {
+    using MathLibrary for uint256;
+
+    string public usingString = "using Math for uint256;";
+    string public inheritanceString = "\"Contract is Parent {\"";
+    string public castString = 'Peer(smth) ';
+    string public methodString = '\' Math.max()';
+
+    PeerContract public peer;
+
+    error Peer();
+
+    constructor(address _peer) {
+        peer = PeerContract(_peer);
+    }
+
+    function Math(uint256 value) external pure returns (uint256) {
+        return MathLibrary.minusOne(MathLibrary.max() - value.diffMax());
+    }
+}
+"#
+    );
+}
+
 #[test]
 fn can_detect_type_error() {
     let project = TempProject::<ConfigurableArtifacts>::dapptools().unwrap();