make ENS a first class citizen
This commit is contained in:
parent
6109003559
commit
3186673a2e
|
@ -1,6 +1,6 @@
|
|||
//! Contract Functions Output types.
|
||||
//! Adapted from: https://github.com/tomusdrw/rust-web3/blob/master/src/contract/tokens.rs
|
||||
#[allow(clippy::all)]
|
||||
#![allow(clippy::all)]
|
||||
use crate::Token;
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{ContractCall, Event};
|
|||
use ethers_abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize};
|
||||
use ethers_providers::{networks::Network, JsonRpcClient};
|
||||
use ethers_signers::{Client, Signer};
|
||||
use ethers_types::{Address, Filter, Selector, TransactionRequest};
|
||||
use ethers_types::{Address, Filter, NameOrAddress, Selector, TransactionRequest};
|
||||
|
||||
use rustc_hex::ToHex;
|
||||
use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
|
||||
|
@ -93,7 +93,7 @@ impl<'a, P: JsonRpcClient, N: Network, S: Signer> Contract<'a, P, N, S> {
|
|||
|
||||
// create the tx object
|
||||
let tx = TransactionRequest {
|
||||
to: Some(self.address),
|
||||
to: Some(NameOrAddress::Address(self.address)),
|
||||
data: Some(data.into()),
|
||||
..Default::default()
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Adapted from https://github.com/hhatto/rust-ens/blob/master/src/lib.rs
|
||||
use ethers_types::{Address, Selector, TransactionRequest, H256};
|
||||
use ethers_types::{Address, NameOrAddress, Selector, TransactionRequest, H256};
|
||||
use ethers_utils::keccak256;
|
||||
|
||||
// Selectors
|
||||
|
@ -21,7 +21,7 @@ pub fn get_resolver<T: Into<Address>>(ens_address: T, name: &str) -> Transaction
|
|||
let data = [&RESOLVER[..], &namehash(name).0].concat();
|
||||
TransactionRequest {
|
||||
data: Some(data.into()),
|
||||
to: Some(ens_address.into()),
|
||||
to: Some(NameOrAddress::Address(ens_address.into())),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ pub fn resolve<T: Into<Address>>(
|
|||
let data = [&selector[..], &namehash(name).0].concat();
|
||||
TransactionRequest {
|
||||
data: Some(data.into()),
|
||||
to: Some(resolver_address.into()),
|
||||
to: Some(NameOrAddress::Address(resolver_address.into())),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::{ens, http::Provider as HttpProvider, networks::Network, JsonRpcClien
|
|||
|
||||
use ethers_abi::{Detokenize, ParamType};
|
||||
use ethers_types::{
|
||||
Address, Block, BlockId, BlockNumber, Bytes, Filter, Log, Selector, Transaction,
|
||||
Address, Block, BlockId, BlockNumber, Bytes, Filter, Log, NameOrAddress, Selector, Transaction,
|
||||
TransactionReceipt, TransactionRequest, TxHash, U256,
|
||||
};
|
||||
use ethers_utils as utils;
|
||||
|
@ -155,7 +155,17 @@ impl<P: JsonRpcClient, N: Network> Provider<P, N> {
|
|||
|
||||
/// 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> {
|
||||
pub async fn send_transaction(&self, mut tx: TransactionRequest) -> Result<TxHash, P::Error> {
|
||||
if let Some(ref to) = tx.to {
|
||||
if let NameOrAddress::Name(ens_name) = to {
|
||||
let addr = self
|
||||
.resolve_name(&ens_name)
|
||||
.await?
|
||||
.expect("TODO: Handle ENS name not found");
|
||||
tx.to = Some(addr.into())
|
||||
}
|
||||
}
|
||||
|
||||
self.0.request("eth_sendTransaction", Some(tx)).await
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::Signer;
|
||||
|
||||
use ethers_providers::{networks::Network, JsonRpcClient, Provider};
|
||||
use ethers_types::{Address, BlockNumber, TransactionRequest, TxHash};
|
||||
use ethers_types::{Address, BlockNumber, NameOrAddress, TransactionRequest, TxHash};
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
|
@ -33,6 +33,16 @@ where
|
|||
mut tx: TransactionRequest,
|
||||
block: Option<BlockNumber>,
|
||||
) -> Result<TxHash, P::Error> {
|
||||
if let Some(ref to) = tx.to {
|
||||
if let NameOrAddress::Name(ens_name) = to {
|
||||
let addr = self
|
||||
.resolve_name(&ens_name)
|
||||
.await?
|
||||
.expect("TODO: Handle ENS name not found");
|
||||
tx.to = Some(addr.into())
|
||||
}
|
||||
}
|
||||
|
||||
// if there is no local signer, then the transaction should use the
|
||||
// node's signer which should already be unlocked
|
||||
let signer = if let Some(ref signer) = self.signer {
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
use crate::Address;
|
||||
use rlp::{Encodable, RlpStream};
|
||||
use serde::{ser::Error as SerializationError, Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// ENS name or Ethereum Address. Not RLP encoded/serialized if it's a name
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum NameOrAddress {
|
||||
Name(String),
|
||||
Address(Address),
|
||||
}
|
||||
|
||||
// Only RLP encode the Address variant since it doesn't make sense to ever RLP encode
|
||||
// an ENS name
|
||||
impl Encodable for &NameOrAddress {
|
||||
fn rlp_append(&self, s: &mut RlpStream) {
|
||||
if let NameOrAddress::Address(inner) = self {
|
||||
inner.rlp_append(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for NameOrAddress {
|
||||
fn from(s: &str) -> Self {
|
||||
NameOrAddress::Name(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Address> for NameOrAddress {
|
||||
fn from(s: Address) -> Self {
|
||||
NameOrAddress::Address(s)
|
||||
}
|
||||
}
|
||||
|
||||
// Only serialize the Address variant since it doesn't make sense to ever serialize
|
||||
// an ENS name
|
||||
impl Serialize for NameOrAddress {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
NameOrAddress::Address(addr) => addr.serialize(serializer),
|
||||
NameOrAddress::Name(name) => Err(SerializationError::custom(format!(
|
||||
"cannot serialize ENS name {}, must be address",
|
||||
name
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NameOrAddress {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let inner = Address::deserialize(deserializer)?;
|
||||
|
||||
Ok(NameOrAddress::Address(inner))
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{Address, Signature, Transaction, TransactionRequest, H256, U256, U64};
|
||||
use crate::{Address, NameOrAddress, Signature, Transaction, TransactionRequest, H256, U256, U64};
|
||||
use ethers_utils::{hash_message, keccak256};
|
||||
|
||||
use rand::Rng;
|
||||
|
@ -63,6 +63,11 @@ impl PrivateKey {
|
|||
///
|
||||
/// This will return an error if called if any of the `nonce`, `gas_price` or `gas`
|
||||
/// fields are not populated.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `tx.to` is an ENS name. The caller MUST take care of naem resolution before
|
||||
/// calling this function.
|
||||
pub fn sign_transaction(
|
||||
&self,
|
||||
tx: TransactionRequest,
|
||||
|
@ -82,11 +87,18 @@ impl PrivateKey {
|
|||
let rlp = tx.rlp_signed(&signature);
|
||||
let hash = keccak256(&rlp.0);
|
||||
|
||||
let to = tx.to.map(|to| match to {
|
||||
NameOrAddress::Address(inner) => inner,
|
||||
NameOrAddress::Name(_) => {
|
||||
panic!("Expected `to` to be an Ethereum Address, not an ENS name")
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Transaction {
|
||||
hash: hash.into(),
|
||||
nonce,
|
||||
from: self.into(),
|
||||
to: tx.to,
|
||||
to,
|
||||
value: tx.value.unwrap_or_default(),
|
||||
gas_price,
|
||||
gas,
|
||||
|
@ -217,7 +229,12 @@ mod tests {
|
|||
// https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
|
||||
let tx = TransactionRequest {
|
||||
from: None,
|
||||
to: Some("F0109fC8DF283027b6285cc889F5aA624EaC1F55".parse().unwrap()),
|
||||
to: Some(
|
||||
"F0109fC8DF283027b6285cc889F5aA624EaC1F55"
|
||||
.parse::<Address>()
|
||||
.unwrap()
|
||||
.into(),
|
||||
),
|
||||
value: Some(1_000_000_000.into()),
|
||||
gas: Some(2_000_000.into()),
|
||||
nonce: Some(0.into()),
|
||||
|
|
|
@ -24,6 +24,9 @@ pub use block::{Block, BlockId, BlockNumber};
|
|||
mod log;
|
||||
pub use log::{Filter, Log, ValueOrArray};
|
||||
|
||||
mod ens;
|
||||
pub use ens::NameOrAddress;
|
||||
|
||||
// re-export the non-standard rand version so that other crates don't use the
|
||||
// wrong one by accident
|
||||
pub use rand;
|
||||
|
|
|
@ -89,16 +89,16 @@ pub struct Filter {
|
|||
|
||||
impl Filter {
|
||||
pub fn new() -> Self {
|
||||
let filter = Self::default();
|
||||
// filter.topics = vec![H256::zero().into(); 4];
|
||||
filter
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||
self.from_block = Some(block.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(clippy::wrong_self_convention)]
|
||||
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||
self.to_block = Some(block.into());
|
||||
self
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//! Transaction types
|
||||
use crate::{Address, Bloom, Bytes, Log, Signature, H256, U256, U64};
|
||||
use crate::{Address, Bloom, Bytes, Log, NameOrAddress, Signature, H256, U256, U64};
|
||||
use ethers_utils::keccak256;
|
||||
|
||||
use rlp::RlpStream;
|
||||
|
@ -40,7 +40,7 @@ pub struct TransactionRequest {
|
|||
|
||||
/// Recipient address (None for contract creation)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub to: Option<Address>,
|
||||
pub to: Option<NameOrAddress>,
|
||||
|
||||
/// Supplied gas (None for sensible default)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
@ -73,7 +73,7 @@ impl TransactionRequest {
|
|||
|
||||
/// Convenience function for sending a new payment transaction to the receiver. The
|
||||
/// `gas`, `gas_price` and `nonce` fields are left empty, to be populated
|
||||
pub fn pay<T: Into<Address>, V: Into<U256>>(to: T, value: V) -> Self {
|
||||
pub fn pay<T: Into<NameOrAddress>, V: Into<U256>>(to: T, value: V) -> Self {
|
||||
TransactionRequest {
|
||||
from: None,
|
||||
to: Some(to.into()),
|
||||
|
@ -95,12 +95,12 @@ impl TransactionRequest {
|
|||
|
||||
/// Sets the `to` field in the transaction to the provided value
|
||||
pub fn send_to_str(mut self, to: &str) -> Result<Self, rustc_hex::FromHexError> {
|
||||
self.to = Some(Address::from_str(to)?);
|
||||
self.to = Some(Address::from_str(to)?.into());
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Sets the `to` field in the transaction to the provided value
|
||||
pub fn to<T: Into<Address>>(mut self, to: T) -> Self {
|
||||
pub fn to<T: Into<NameOrAddress>>(mut self, to: T) -> Self {
|
||||
self.to = Some(to.into());
|
||||
self
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ impl TransactionRequest {
|
|||
rlp_opt(rlp, self.nonce);
|
||||
rlp_opt(rlp, self.gas_price);
|
||||
rlp_opt(rlp, self.gas);
|
||||
rlp_opt(rlp, self.to);
|
||||
rlp_opt(rlp, self.to.as_ref());
|
||||
rlp_opt(rlp, self.value);
|
||||
rlp_opt(rlp, self.data.as_ref().map(|d| &d.0[..]));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
use anyhow::Result;
|
||||
use ethers::{providers::HttpProvider, signers::MainnetWallet, types::TransactionRequest};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
// connect to the network
|
||||
let provider =
|
||||
HttpProvider::try_from("https://mainnet.infura.io/v3/9408f47dedf04716a03ef994182cf150")?;
|
||||
|
||||
// create a wallet and connect it to the provider
|
||||
let client = "dcf2cbdd171a21c480aa7f53d77f31bb102282b3ff099c78e3118b37348c72f7"
|
||||
.parse::<MainnetWallet>()?
|
||||
.connect(&provider);
|
||||
|
||||
// craft the transaction
|
||||
let tx = TransactionRequest::new().to("vitalik.eth").value(100_000);
|
||||
|
||||
// send it!
|
||||
let hash = client.send_transaction(tx, None).await?;
|
||||
|
||||
// get the mined tx
|
||||
let tx = client.get_transaction(hash).await?;
|
||||
|
||||
let receipt = client.get_transaction_receipt(tx.hash).await?;
|
||||
|
||||
println!("{}", serde_json::to_string(&tx)?);
|
||||
println!("{}", serde_json::to_string(&receipt)?);
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue