make ENS a first class citizen
This commit is contained in:
parent
6109003559
commit
3186673a2e
|
@ -1,6 +1,6 @@
|
||||||
//! Contract Functions Output types.
|
//! Contract Functions Output types.
|
||||||
//! Adapted from: https://github.com/tomusdrw/rust-web3/blob/master/src/contract/tokens.rs
|
//! Adapted from: https://github.com/tomusdrw/rust-web3/blob/master/src/contract/tokens.rs
|
||||||
#[allow(clippy::all)]
|
#![allow(clippy::all)]
|
||||||
use crate::Token;
|
use crate::Token;
|
||||||
|
|
||||||
use arrayvec::ArrayVec;
|
use arrayvec::ArrayVec;
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::{ContractCall, Event};
|
||||||
use ethers_abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize};
|
use ethers_abi::{Abi, Detokenize, Error, EventExt, Function, FunctionExt, Tokenize};
|
||||||
use ethers_providers::{networks::Network, JsonRpcClient};
|
use ethers_providers::{networks::Network, JsonRpcClient};
|
||||||
use ethers_signers::{Client, Signer};
|
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 rustc_hex::ToHex;
|
||||||
use std::{collections::HashMap, fmt::Debug, hash::Hash, marker::PhantomData};
|
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
|
// create the tx object
|
||||||
let tx = TransactionRequest {
|
let tx = TransactionRequest {
|
||||||
to: Some(self.address),
|
to: Some(NameOrAddress::Address(self.address)),
|
||||||
data: Some(data.into()),
|
data: Some(data.into()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// Adapted from https://github.com/hhatto/rust-ens/blob/master/src/lib.rs
|
// 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;
|
use ethers_utils::keccak256;
|
||||||
|
|
||||||
// Selectors
|
// 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();
|
let data = [&RESOLVER[..], &namehash(name).0].concat();
|
||||||
TransactionRequest {
|
TransactionRequest {
|
||||||
data: Some(data.into()),
|
data: Some(data.into()),
|
||||||
to: Some(ens_address.into()),
|
to: Some(NameOrAddress::Address(ens_address.into())),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ pub fn resolve<T: Into<Address>>(
|
||||||
let data = [&selector[..], &namehash(name).0].concat();
|
let data = [&selector[..], &namehash(name).0].concat();
|
||||||
TransactionRequest {
|
TransactionRequest {
|
||||||
data: Some(data.into()),
|
data: Some(data.into()),
|
||||||
to: Some(resolver_address.into()),
|
to: Some(NameOrAddress::Address(resolver_address.into())),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{ens, http::Provider as HttpProvider, networks::Network, JsonRpcClien
|
||||||
|
|
||||||
use ethers_abi::{Detokenize, ParamType};
|
use ethers_abi::{Detokenize, ParamType};
|
||||||
use ethers_types::{
|
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,
|
TransactionReceipt, TransactionRequest, TxHash, U256,
|
||||||
};
|
};
|
||||||
use ethers_utils as utils;
|
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
|
/// 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.
|
/// 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
|
self.0.request("eth_sendTransaction", Some(tx)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::Signer;
|
use crate::Signer;
|
||||||
|
|
||||||
use ethers_providers::{networks::Network, JsonRpcClient, Provider};
|
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;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
@ -33,6 +33,16 @@ where
|
||||||
mut tx: TransactionRequest,
|
mut tx: TransactionRequest,
|
||||||
block: Option<BlockNumber>,
|
block: Option<BlockNumber>,
|
||||||
) -> Result<TxHash, P::Error> {
|
) -> 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
|
// if there is no local signer, then the transaction should use the
|
||||||
// node's signer which should already be unlocked
|
// node's signer which should already be unlocked
|
||||||
let signer = if let Some(ref signer) = self.signer {
|
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 ethers_utils::{hash_message, keccak256};
|
||||||
|
|
||||||
use rand::Rng;
|
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`
|
/// This will return an error if called if any of the `nonce`, `gas_price` or `gas`
|
||||||
/// fields are not populated.
|
/// 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(
|
pub fn sign_transaction(
|
||||||
&self,
|
&self,
|
||||||
tx: TransactionRequest,
|
tx: TransactionRequest,
|
||||||
|
@ -82,11 +87,18 @@ impl PrivateKey {
|
||||||
let rlp = tx.rlp_signed(&signature);
|
let rlp = tx.rlp_signed(&signature);
|
||||||
let hash = keccak256(&rlp.0);
|
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 {
|
Ok(Transaction {
|
||||||
hash: hash.into(),
|
hash: hash.into(),
|
||||||
nonce,
|
nonce,
|
||||||
from: self.into(),
|
from: self.into(),
|
||||||
to: tx.to,
|
to,
|
||||||
value: tx.value.unwrap_or_default(),
|
value: tx.value.unwrap_or_default(),
|
||||||
gas_price,
|
gas_price,
|
||||||
gas,
|
gas,
|
||||||
|
@ -217,7 +229,12 @@ mod tests {
|
||||||
// https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
|
// https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
|
||||||
let tx = TransactionRequest {
|
let tx = TransactionRequest {
|
||||||
from: None,
|
from: None,
|
||||||
to: Some("F0109fC8DF283027b6285cc889F5aA624EaC1F55".parse().unwrap()),
|
to: Some(
|
||||||
|
"F0109fC8DF283027b6285cc889F5aA624EaC1F55"
|
||||||
|
.parse::<Address>()
|
||||||
|
.unwrap()
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
value: Some(1_000_000_000.into()),
|
value: Some(1_000_000_000.into()),
|
||||||
gas: Some(2_000_000.into()),
|
gas: Some(2_000_000.into()),
|
||||||
nonce: Some(0.into()),
|
nonce: Some(0.into()),
|
||||||
|
|
|
@ -24,6 +24,9 @@ pub use block::{Block, BlockId, BlockNumber};
|
||||||
mod log;
|
mod log;
|
||||||
pub use log::{Filter, Log, ValueOrArray};
|
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
|
// re-export the non-standard rand version so that other crates don't use the
|
||||||
// wrong one by accident
|
// wrong one by accident
|
||||||
pub use rand;
|
pub use rand;
|
||||||
|
|
|
@ -89,16 +89,16 @@ pub struct Filter {
|
||||||
|
|
||||||
impl Filter {
|
impl Filter {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let filter = Self::default();
|
Self::default()
|
||||||
// filter.topics = vec![H256::zero().into(); 4];
|
|
||||||
filter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
self.from_block = Some(block.into());
|
self.from_block = Some(block.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::wrong_self_convention)]
|
||||||
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
|
||||||
self.to_block = Some(block.into());
|
self.to_block = Some(block.into());
|
||||||
self
|
self
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//! Transaction types
|
//! 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 ethers_utils::keccak256;
|
||||||
|
|
||||||
use rlp::RlpStream;
|
use rlp::RlpStream;
|
||||||
|
@ -40,7 +40,7 @@ pub struct TransactionRequest {
|
||||||
|
|
||||||
/// Recipient address (None for contract creation)
|
/// Recipient address (None for contract creation)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub to: Option<Address>,
|
pub to: Option<NameOrAddress>,
|
||||||
|
|
||||||
/// Supplied gas (None for sensible default)
|
/// Supplied gas (None for sensible default)
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[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
|
/// Convenience function for sending a new payment transaction to the receiver. The
|
||||||
/// `gas`, `gas_price` and `nonce` fields are left empty, to be populated
|
/// `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 {
|
TransactionRequest {
|
||||||
from: None,
|
from: None,
|
||||||
to: Some(to.into()),
|
to: Some(to.into()),
|
||||||
|
@ -95,12 +95,12 @@ impl TransactionRequest {
|
||||||
|
|
||||||
/// Sets the `to` field in the transaction to the provided value
|
/// 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> {
|
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)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the `to` field in the transaction to the provided value
|
/// 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.to = Some(to.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -165,7 +165,7 @@ impl TransactionRequest {
|
||||||
rlp_opt(rlp, self.nonce);
|
rlp_opt(rlp, self.nonce);
|
||||||
rlp_opt(rlp, self.gas_price);
|
rlp_opt(rlp, self.gas_price);
|
||||||
rlp_opt(rlp, self.gas);
|
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.value);
|
||||||
rlp_opt(rlp, self.data.as_ref().map(|d| &d.0[..]));
|
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