feat: add ens support
This commit is contained in:
parent
f42aaf2588
commit
bd6fee59cb
|
@ -299,9 +299,11 @@ dependencies = [
|
||||||
"ethers-types 0.1.0",
|
"ethers-types 0.1.0",
|
||||||
"ethers-utils 0.1.0",
|
"ethers-utils 0.1.0",
|
||||||
"reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"reqwest 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rustc-hex 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
"serde_json 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"tokio 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -15,3 +15,7 @@ serde = { version = "1.0.110", default-features = false, features = ["derive"] }
|
||||||
serde_json = { version = "1.0.53", default-features = false }
|
serde_json = { version = "1.0.53", default-features = false }
|
||||||
thiserror = { version = "1.0.19", default-features = false }
|
thiserror = { version = "1.0.19", default-features = false }
|
||||||
url = { version = "2.1.1", default-features = false }
|
url = { version = "2.1.1", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rustc-hex = "2.1.0"
|
||||||
|
tokio = { version = "0.2.21", features = ["macros"] }
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
// Adapted from https://github.com/hhatto/rust-ens/blob/master/src/lib.rs
|
||||||
|
use ethers_types::{Address, Selector, TransactionRequest, H256};
|
||||||
|
use ethers_utils::keccak256;
|
||||||
|
|
||||||
|
// Selectors
|
||||||
|
|
||||||
|
const ENS_REVERSE_REGISTRAR_DOMAIN: &str = "addr.reverse";
|
||||||
|
|
||||||
|
// resolver(bytes32)
|
||||||
|
const RESOLVER: Selector = [1, 120, 184, 191];
|
||||||
|
|
||||||
|
// addr(bytes32)
|
||||||
|
pub const ADDR_SELECTOR: Selector = [59, 59, 87, 222];
|
||||||
|
|
||||||
|
// name(bytes32)
|
||||||
|
pub const NAME_SELECTOR: Selector = [105, 31, 52, 49];
|
||||||
|
|
||||||
|
/// Returns a transaction request for calling the `resolver` method on the ENS server
|
||||||
|
pub fn get_resolver<T: Into<Address>>(ens_address: T, name: &str) -> TransactionRequest {
|
||||||
|
// keccak256('resolver(bytes32)')
|
||||||
|
let data = [&RESOLVER[..], &namehash(name).0].concat();
|
||||||
|
TransactionRequest {
|
||||||
|
data: Some(data.into()),
|
||||||
|
to: Some(ens_address.into()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve<T: Into<Address>>(
|
||||||
|
resolver_address: T,
|
||||||
|
selector: Selector,
|
||||||
|
name: &str,
|
||||||
|
) -> TransactionRequest {
|
||||||
|
let data = [&selector[..], &namehash(name).0].concat();
|
||||||
|
TransactionRequest {
|
||||||
|
data: Some(data.into()),
|
||||||
|
to: Some(resolver_address.into()),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reverse_address(addr: Address) -> String {
|
||||||
|
format!("{:?}.{}", addr, ENS_REVERSE_REGISTRAR_DOMAIN)[2..].to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the ENS namehash as specified in [EIP-137](https://eips.ethereum.org/EIPS/eip-137)
|
||||||
|
pub fn namehash(name: &str) -> H256 {
|
||||||
|
if name.is_empty() {
|
||||||
|
return H256::zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate in reverse
|
||||||
|
name.rsplit('.')
|
||||||
|
.fold([0u8; 32], |node, label| {
|
||||||
|
keccak256(&[node, keccak256(label.as_bytes())].concat())
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use rustc_hex::FromHex;
|
||||||
|
|
||||||
|
fn assert_hex(hash: H256, val: &str) {
|
||||||
|
let v = if val.starts_with("0x") {
|
||||||
|
&val[2..]
|
||||||
|
} else {
|
||||||
|
val
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(hash.0.to_vec(), v.from_hex::<Vec<u8>>().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_namehash() {
|
||||||
|
dbg!(ethers_utils::id("name(bytes32)"));
|
||||||
|
for (name, expected) in &[
|
||||||
|
(
|
||||||
|
"",
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"foo.eth",
|
||||||
|
"de9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"eth",
|
||||||
|
"0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"alice.eth",
|
||||||
|
"0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec",
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
assert_hex(namehash(name), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,9 @@
|
||||||
mod http;
|
mod http;
|
||||||
mod provider;
|
mod provider;
|
||||||
|
|
||||||
|
/// ENS support
|
||||||
|
pub mod ens;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{error::Error, fmt::Debug};
|
use std::{error::Error, fmt::Debug};
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
use crate::{ens, http::Provider as HttpProvider, JsonRpcClient};
|
||||||
|
|
||||||
|
use ethers_abi::{Detokenize, ParamType};
|
||||||
use ethers_types::{
|
use ethers_types::{
|
||||||
Address, Block, BlockId, BlockNumber, Bytes, Filter, Log, Transaction, TransactionReceipt,
|
Address, Block, BlockId, BlockNumber, Bytes, Filter, Log, Selector, Transaction,
|
||||||
TransactionRequest, TxHash, U256,
|
TransactionReceipt, TransactionRequest, TxHash, U256,
|
||||||
};
|
};
|
||||||
use ethers_utils as utils;
|
use ethers_utils as utils;
|
||||||
|
|
||||||
use crate::{http::Provider as HttpProvider, JsonRpcClient};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
|
@ -13,55 +15,33 @@ use std::{convert::TryFrom, fmt::Debug};
|
||||||
/// An abstract provider for interacting with the [Ethereum JSON RPC
|
/// An abstract provider for interacting with the [Ethereum JSON RPC
|
||||||
/// API](https://github.com/ethereum/wiki/wiki/JSON-RPC)
|
/// API](https://github.com/ethereum/wiki/wiki/JSON-RPC)
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Provider<P>(P);
|
pub struct Provider<P>(P, Option<Address>);
|
||||||
|
|
||||||
// JSON RPC bindings
|
// JSON RPC bindings
|
||||||
impl<P: JsonRpcClient> Provider<P> {
|
impl<P: JsonRpcClient> Provider<P> {
|
||||||
/// Gets the current gas price as estimated by the node
|
////// Blockchain Status
|
||||||
pub async fn get_gas_price(&self) -> Result<U256, P::Error> {
|
//
|
||||||
self.0.request("eth_gasPrice", None::<()>).await
|
// Functions for querying the state of the blockchain
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to estimate the gas for the transaction
|
|
||||||
pub async fn estimate_gas(
|
|
||||||
&self,
|
|
||||||
tx: &TransactionRequest,
|
|
||||||
block: Option<BlockNumber>,
|
|
||||||
) -> Result<U256, P::Error> {
|
|
||||||
let tx = utils::serialize(tx);
|
|
||||||
|
|
||||||
let args = match block {
|
|
||||||
Some(block) => vec![tx, utils::serialize(&block)],
|
|
||||||
None => vec![tx],
|
|
||||||
};
|
|
||||||
|
|
||||||
self.0.request("eth_estimateGas", Some(args)).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the logs matching a given filter
|
|
||||||
pub async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>, P::Error> {
|
|
||||||
self.0.request("eth_getLogs", Some(filter)).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the accounts on the node
|
|
||||||
pub async fn get_accounts(&self) -> Result<Vec<Address>, P::Error> {
|
|
||||||
self.0.request("eth_accounts", None::<()>).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the latest block number via the `eth_BlockNumber` API
|
/// Gets the latest block number via the `eth_BlockNumber` API
|
||||||
pub async fn get_block_number(&self) -> Result<U256, P::Error> {
|
pub async fn get_block_number(&self) -> Result<U256, P::Error> {
|
||||||
self.0.request("eth_blockNumber", None::<()>).await
|
self.0.request("eth_blockNumber", None::<()>).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_block(&self, id: impl Into<BlockId>) -> Result<Block<TxHash>, P::Error> {
|
/// Gets the block at `block_hash_or_number` (transaction hashes only)
|
||||||
self.get_block_gen(id.into(), false).await
|
pub async fn get_block(
|
||||||
|
&self,
|
||||||
|
block_hash_or_number: impl Into<BlockId>,
|
||||||
|
) -> Result<Block<TxHash>, P::Error> {
|
||||||
|
self.get_block_gen(block_hash_or_number.into(), false).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the block at `block_hash_or_number` (full transactions included)
|
||||||
pub async fn get_block_with_txs(
|
pub async fn get_block_with_txs(
|
||||||
&self,
|
&self,
|
||||||
id: impl Into<BlockId>,
|
block_hash_or_number: impl Into<BlockId>,
|
||||||
) -> Result<Block<Transaction>, P::Error> {
|
) -> Result<Block<Transaction>, P::Error> {
|
||||||
self.get_block_gen(id.into(), true).await
|
self.get_block_gen(block_hash_or_number.into(), true).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_block_gen<Tx: for<'a> Deserialize<'a>>(
|
async fn get_block_gen<Tx: for<'a> Deserialize<'a>>(
|
||||||
|
@ -85,52 +65,37 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the transaction receipt for tx hash
|
/// Gets the transaction with `transaction_hash`
|
||||||
|
pub async fn get_transaction<T: Send + Sync + Into<TxHash>>(
|
||||||
|
&self,
|
||||||
|
transaction_hash: T,
|
||||||
|
) -> Result<Transaction, P::Error> {
|
||||||
|
let hash = transaction_hash.into();
|
||||||
|
self.0.request("eth_getTransactionByHash", Some(hash)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the transaction receipt with `transaction_hash`
|
||||||
pub async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
|
pub async fn get_transaction_receipt<T: Send + Sync + Into<TxHash>>(
|
||||||
&self,
|
&self,
|
||||||
hash: T,
|
transaction_hash: T,
|
||||||
) -> Result<TransactionReceipt, P::Error> {
|
) -> Result<TransactionReceipt, P::Error> {
|
||||||
let hash = hash.into();
|
let hash = transaction_hash.into();
|
||||||
self.0
|
self.0
|
||||||
.request("eth_getTransactionReceipt", Some(hash))
|
.request("eth_getTransactionReceipt", Some(hash))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the transaction which matches the provided hash via the `eth_getTransactionByHash` API
|
/// Gets the current gas price as estimated by the node
|
||||||
pub async fn get_transaction<T: Send + Sync + Into<TxHash>>(
|
pub async fn get_gas_price(&self) -> Result<U256, P::Error> {
|
||||||
&self,
|
self.0.request("eth_gasPrice", None::<()>).await
|
||||||
hash: T,
|
|
||||||
) -> Result<Transaction, P::Error> {
|
|
||||||
let hash = hash.into();
|
|
||||||
self.0.request("eth_getTransactionByHash", Some(hash)).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// State mutations
|
/// Gets the accounts on the node
|
||||||
|
pub async fn get_accounts(&self) -> Result<Vec<Address>, P::Error> {
|
||||||
/// Broadcasts the transaction request via the `eth_sendTransaction` API
|
self.0.request("eth_accounts", None::<()>).await
|
||||||
pub async fn call(
|
|
||||||
&self,
|
|
||||||
tx: TransactionRequest,
|
|
||||||
block: Option<BlockNumber>,
|
|
||||||
) -> Result<Bytes, P::Error> {
|
|
||||||
let tx = utils::serialize(&tx);
|
|
||||||
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
|
|
||||||
self.0.request("eth_call", Some(vec![tx, block])).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Broadcasts the transaction request via the `eth_sendTransaction` API
|
/// Returns the nonce of the address
|
||||||
pub async fn send_transaction(&self, tx: TransactionRequest) -> Result<TxHash, P::Error> {
|
|
||||||
self.0.request("eth_sendTransaction", Some(tx)).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Broadcasts a raw RLP encoded transaction via the `eth_sendRawTransaction` API
|
|
||||||
pub async fn send_raw_transaction(&self, tx: &Transaction) -> Result<TxHash, P::Error> {
|
|
||||||
let rlp = utils::serialize(&tx.rlp());
|
|
||||||
self.0.request("eth_sendRawTransaction", Some(rlp)).await
|
|
||||||
}
|
|
||||||
|
|
||||||
// Account state
|
|
||||||
|
|
||||||
pub async fn get_transaction_count(
|
pub async fn get_transaction_count(
|
||||||
&self,
|
&self,
|
||||||
from: Address,
|
from: Address,
|
||||||
|
@ -143,6 +108,7 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the account's balance
|
||||||
pub async fn get_balance(
|
pub async fn get_balance(
|
||||||
&self,
|
&self,
|
||||||
from: Address,
|
from: Address,
|
||||||
|
@ -152,12 +118,207 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
|
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
|
||||||
self.0.request("eth_getBalance", Some(&[from, block])).await
|
self.0.request("eth_getBalance", Some(&[from, block])).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
////// Contract Execution
|
||||||
|
//
|
||||||
|
// These are relatively low-level calls. The Contracts API should usually be used instead.
|
||||||
|
|
||||||
|
/// Send the read-only (constant) transaction to a single Ethereum node and return the result (as bytes) of executing it.
|
||||||
|
/// This is free, since it does not change any state on the blockchain.
|
||||||
|
pub async fn call(
|
||||||
|
&self,
|
||||||
|
tx: TransactionRequest,
|
||||||
|
block: Option<BlockNumber>,
|
||||||
|
) -> Result<Bytes, P::Error> {
|
||||||
|
let tx = utils::serialize(&tx);
|
||||||
|
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
|
||||||
|
self.0.request("eth_call", Some(vec![tx, block])).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a transaction to a single Ethereum node and return the estimated amount of gas required (as a U256) to send it
|
||||||
|
/// This is free, but only an estimate. Providing too little gas will result in a transaction being rejected
|
||||||
|
/// (while still consuming all provided gas).
|
||||||
|
pub async fn estimate_gas(
|
||||||
|
&self,
|
||||||
|
tx: &TransactionRequest,
|
||||||
|
block: Option<BlockNumber>,
|
||||||
|
) -> Result<U256, P::Error> {
|
||||||
|
let tx = utils::serialize(tx);
|
||||||
|
|
||||||
|
let args = match block {
|
||||||
|
Some(block) => vec![tx, utils::serialize(&block)],
|
||||||
|
None => vec![tx],
|
||||||
|
};
|
||||||
|
|
||||||
|
self.0.request("eth_estimateGas", Some(args)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send the transaction to the entire Ethereum network and returns the transaction's hash
|
||||||
|
/// This will consume gas from the account that signed the transaction.
|
||||||
|
pub async fn send_transaction(&self, tx: TransactionRequest) -> Result<TxHash, P::Error> {
|
||||||
|
self.0.request("eth_sendTransaction", Some(tx)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send the raw RLP encoded transaction to the entire Ethereum network and returns the transaction's hash
|
||||||
|
/// This will consume gas from the account that signed the transaction.
|
||||||
|
pub async fn send_raw_transaction(&self, tx: &Transaction) -> Result<TxHash, P::Error> {
|
||||||
|
let rlp = utils::serialize(&tx.rlp());
|
||||||
|
self.0.request("eth_sendRawTransaction", Some(rlp)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
////// Contract state
|
||||||
|
|
||||||
|
/// Returns an array (possibly empty) of logs that match the filter
|
||||||
|
pub async fn get_logs(&self, filter: &Filter) -> Result<Vec<Log>, P::Error> {
|
||||||
|
self.0.request("eth_getLogs", Some(filter)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: get_code, get_storage_at
|
||||||
|
|
||||||
|
////// Ethereum Naming Service
|
||||||
|
// The Ethereum Naming Service (ENS) allows easy to remember and use names to
|
||||||
|
// be assigned to Ethereum addresses. Any provider operation which takes an address
|
||||||
|
// may also take an ENS name.
|
||||||
|
//
|
||||||
|
// ENS also provides the ability for a reverse lookup, which determines the name for an address if it has been configured.
|
||||||
|
|
||||||
|
/// Returns the address that the `ens_name` resolves to (or None if not configured).
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If the bytes returned from the ENS registrar/resolver cannot be interpreted as
|
||||||
|
/// an address. This should theoretically never happen.
|
||||||
|
pub async fn resolve_name(&self, ens_name: &str) -> Result<Option<Address>, P::Error> {
|
||||||
|
self.query_resolver(ParamType::Address, ens_name, ens::ADDR_SELECTOR)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the ENS name the `address` resolves to (or None if not configured).
|
||||||
|
pub async fn lookup_address(&self, address: Address) -> Result<Option<String>, P::Error> {
|
||||||
|
let ens_name = ens::reverse_address(address);
|
||||||
|
self.query_resolver(ParamType::String, &ens_name, ens::NAME_SELECTOR)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn query_resolver<T: Detokenize>(
|
||||||
|
&self,
|
||||||
|
param: ParamType,
|
||||||
|
ens_name: &str,
|
||||||
|
selector: Selector,
|
||||||
|
) -> Result<Option<T>, P::Error> {
|
||||||
|
let ens_addr = if let Some(ens_addr) = self.1 {
|
||||||
|
ens_addr
|
||||||
|
} else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
// first get the resolver responsible for this name
|
||||||
|
// the call will return a Bytes array which we convert to an address
|
||||||
|
let data = self
|
||||||
|
.call(ens::get_resolver(ens_addr, ens_name), None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let resolver_address: Address = decode_bytes(ParamType::Address, data);
|
||||||
|
if resolver_address == Address::zero() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve
|
||||||
|
let data = self
|
||||||
|
.call(ens::resolve(resolver_address, selector, ens_name), None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Some(decode_bytes(param, data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ens<T: Into<Address>>(mut self, ens_addr: T) -> Self {
|
||||||
|
self.1 = Some(ens_addr.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// infallbile conversion of Bytes to Address/String
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If the provided bytes were not an interpretation of an address
|
||||||
|
fn decode_bytes<T: Detokenize>(param: ParamType, bytes: Bytes) -> T {
|
||||||
|
let tokens = ethers_abi::decode(&[param], &bytes.0)
|
||||||
|
.expect("could not abi-decode bytes to address tokens");
|
||||||
|
T::from_tokens(tokens).expect("could not parse tokens as address")
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for Provider<HttpProvider> {
|
impl TryFrom<&str> for Provider<HttpProvider> {
|
||||||
type Error = ParseError;
|
type Error = ParseError;
|
||||||
|
|
||||||
fn try_from(src: &str) -> Result<Self, Self::Error> {
|
fn try_from(src: &str) -> Result<Self, Self::Error> {
|
||||||
Ok(Provider(HttpProvider::new(Url::parse(src)?)))
|
Ok(Provider(HttpProvider::new(Url::parse(src)?), None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod ens_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
// Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2
|
||||||
|
async fn mainnet_resolve_name() {
|
||||||
|
let mainnet_ens_addr = "00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
|
||||||
|
.parse::<Address>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let provider = Provider::<HttpProvider>::try_from(
|
||||||
|
"https://mainnet.infura.io/v3/9408f47dedf04716a03ef994182cf150",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.ens(mainnet_ens_addr);
|
||||||
|
|
||||||
|
let addr = provider
|
||||||
|
.resolve_name("registrar.firefly.eth")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
addr.unwrap(),
|
||||||
|
"6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
// registrar not found
|
||||||
|
let addr = provider.resolve_name("asdfasdffads").await.unwrap();
|
||||||
|
assert!(addr.is_none());
|
||||||
|
|
||||||
|
// name not found
|
||||||
|
let addr = provider
|
||||||
|
.resolve_name("asdfasdf.registrar.firefly.eth")
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert!(addr.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
// Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2
|
||||||
|
async fn mainnet_lookup_address() {
|
||||||
|
let mainnet_ens_addr = "00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
|
||||||
|
.parse::<Address>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let provider = Provider::<HttpProvider>::try_from(
|
||||||
|
"https://mainnet.infura.io/v3/9408f47dedf04716a03ef994182cf150",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.ens(mainnet_ens_addr);
|
||||||
|
|
||||||
|
let name = provider
|
||||||
|
.lookup_address("6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(name.unwrap(), "registrar.firefly.eth");
|
||||||
|
|
||||||
|
let name = provider
|
||||||
|
.lookup_address("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".parse().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert!(name.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue