refactor
This commit is contained in:
parent
913b917620
commit
07daa1a891
|
@ -1,20 +1,21 @@
|
||||||
use ethers::providers::{Provider, ProviderTrait};
|
use ethers::providers::{Provider, ProviderTrait};
|
||||||
use ethers::types::{BlockNumber, UnsignedTransaction};
|
use ethers::types::{BlockNumber, UnsignedTransaction};
|
||||||
use ethers::wallet::{Mainnet, Wallet};
|
use ethers::wallet::MainnetWallet;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), failure::Error> {
|
async fn main() -> Result<(), failure::Error> {
|
||||||
let provider = Provider::try_from("http://localhost:8545")?;
|
let provider = Provider::try_from("http://localhost:8545")?;
|
||||||
let signer = Wallet::<Mainnet>::from_str(
|
let client = MainnetWallet::from_str(
|
||||||
"d8ebe1e50cfea1f9961908d9df28e64bb163fee9ee48320361b2eb0a54974269",
|
"d8ebe1e50cfea1f9961908d9df28e64bb163fee9ee48320361b2eb0a54974269",
|
||||||
)?
|
)?
|
||||||
.connect(&provider);
|
.connect(&provider);
|
||||||
|
|
||||||
let nonce = provider
|
let nonce = provider
|
||||||
.get_transaction_count(signer.inner.address, Some(BlockNumber::Latest))
|
.get_transaction_count(client.signer.address, None)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let tx = UnsignedTransaction {
|
let tx = UnsignedTransaction {
|
||||||
to: Some("986eE0C8B91A58e490Ee59718Cca41056Cf55f24".parse().unwrap()),
|
to: Some("986eE0C8B91A58e490Ee59718Cca41056Cf55f24".parse().unwrap()),
|
||||||
gas: 21000.into(),
|
gas: 21000.into(),
|
||||||
|
@ -24,10 +25,9 @@ async fn main() -> Result<(), failure::Error> {
|
||||||
nonce,
|
nonce,
|
||||||
};
|
};
|
||||||
|
|
||||||
let tx = signer.send_transaction(tx).await?;
|
let tx = client.send_transaction(tx).await?;
|
||||||
|
|
||||||
dbg!(tx.hash);
|
let tx = client.get_transaction(tx.hash).await?;
|
||||||
let tx = provider.get_transaction(tx.hash).await?;
|
|
||||||
|
|
||||||
println!("{}", serde_json::to_string(&tx)?);
|
println!("{}", serde_json::to_string(&tx)?);
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ impl ProviderTrait for Provider {
|
||||||
block: Option<BlockNumber>,
|
block: Option<BlockNumber>,
|
||||||
) -> Result<U256, Self::Error> {
|
) -> Result<U256, Self::Error> {
|
||||||
let from = utils::serialize(&from);
|
let from = utils::serialize(&from);
|
||||||
let block = utils::serialize(&block);
|
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
|
||||||
self.0
|
self.0
|
||||||
.request("eth_getTransactionCount", Some(&[from, block]))
|
.request("eth_getTransactionCount", Some(&[from, block]))
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::U64;
|
use super::U64;
|
||||||
|
use serde::{Serialize, Serializer};
|
||||||
|
|
||||||
/// Block Number
|
/// Block Number
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
@ -13,8 +14,6 @@ pub enum BlockNumber {
|
||||||
Number(U64),
|
Number(U64),
|
||||||
}
|
}
|
||||||
|
|
||||||
use serde::{Serialize, Serializer};
|
|
||||||
|
|
||||||
impl<T: Into<U64>> From<T> for BlockNumber {
|
impl<T: Into<U64>> From<T> for BlockNumber {
|
||||||
fn from(num: T) -> Self {
|
fn from(num: T) -> Self {
|
||||||
BlockNumber::Number(num.into())
|
BlockNumber::Number(num.into())
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! Various Ethereum Related Datatypes
|
//! Various Ethereum Related Datatypes
|
||||||
|
|
||||||
// Re-export common ethereum datatypes with more specific names
|
// Re-export common ethereum datatypes with more specific names
|
||||||
|
pub use ethereum_types::H256 as TxHash;
|
||||||
pub use ethereum_types::{Address, H256, U256, U64};
|
pub use ethereum_types::{Address, H256, U256, U64};
|
||||||
|
|
||||||
mod transaction;
|
mod transaction;
|
||||||
|
@ -19,49 +20,3 @@ pub use bytes::Bytes;
|
||||||
|
|
||||||
mod block;
|
mod block;
|
||||||
pub use block::BlockNumber;
|
pub use block::BlockNumber;
|
||||||
|
|
||||||
use rustc_hex::{FromHex, ToHex};
|
|
||||||
use serde::{
|
|
||||||
de::{Error, Unexpected},
|
|
||||||
Deserialize, Deserializer, Serialize, Serializer,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Wrapper type 0round Vec<u8> to deserialize/serialize "0x" prefixed ethereum hex strings
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct TxHash(
|
|
||||||
#[serde(
|
|
||||||
serialize_with = "serialize_h256",
|
|
||||||
deserialize_with = "deserialize_h256"
|
|
||||||
)]
|
|
||||||
pub H256,
|
|
||||||
);
|
|
||||||
|
|
||||||
impl From<H256> for TxHash {
|
|
||||||
fn from(src: H256) -> TxHash {
|
|
||||||
TxHash(src)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn serialize_h256<S, T>(x: T, s: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
T: AsRef<[u8]>,
|
|
||||||
{
|
|
||||||
s.serialize_str(&format!("0x{}", x.as_ref().to_hex::<String>()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize_h256<'de, D>(d: D) -> Result<H256, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let value = String::deserialize(d)?;
|
|
||||||
if value.len() >= 2 && &value[0..2] == "0x" {
|
|
||||||
let slice: Vec<u8> = FromHex::from_hex(&value[2..])
|
|
||||||
.map_err(|e| Error::custom(format!("Invalid hex: {}", e)))?;
|
|
||||||
let mut bytes = [0u8; 32];
|
|
||||||
bytes.copy_from_slice(&slice[..32]);
|
|
||||||
Ok(bytes.into())
|
|
||||||
} else {
|
|
||||||
Err(Error::invalid_value(Unexpected::Str(&value), &"0x prefix"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
159
src/wallet.rs
159
src/wallet.rs
|
@ -1,159 +0,0 @@
|
||||||
use crate::{
|
|
||||||
jsonrpc::ClientError,
|
|
||||||
providers::{Provider, ProviderTrait},
|
|
||||||
types::{Address, PrivateKey, PublicKey, Signature, U64},
|
|
||||||
types::{Transaction, UnsignedTransaction},
|
|
||||||
};
|
|
||||||
use rand::Rng;
|
|
||||||
use std::{marker::PhantomData, str::FromStr};
|
|
||||||
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
/// A keypair
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Wallet<N> {
|
|
||||||
pub private_key: PrivateKey,
|
|
||||||
pub public_key: PublicKey,
|
|
||||||
pub address: Address,
|
|
||||||
network: PhantomData<N>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Network {
|
|
||||||
const CHAIN_ID: Option<U64>;
|
|
||||||
|
|
||||||
// TODO: Default providers?
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct Mainnet;
|
|
||||||
|
|
||||||
impl Network for Mainnet {
|
|
||||||
const CHAIN_ID: Option<U64> = Some(U64([1]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct AnyNet;
|
|
||||||
|
|
||||||
impl Network for AnyNet {
|
|
||||||
const CHAIN_ID: Option<U64> = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No EIP-155 used
|
|
||||||
pub type AnyWallet = Wallet<AnyNet>;
|
|
||||||
|
|
||||||
impl<N: Network> Wallet<N> {
|
|
||||||
/// Creates a new keypair
|
|
||||||
pub fn new<R: Rng>(rng: &mut R) -> Self {
|
|
||||||
let private_key = PrivateKey::new(rng);
|
|
||||||
let public_key = PublicKey::from(&private_key);
|
|
||||||
let address = Address::from(&private_key);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
private_key,
|
|
||||||
public_key,
|
|
||||||
address,
|
|
||||||
network: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Connects to a provider and returns a signer
|
|
||||||
pub fn connect<'a>(self, provider: &'a Provider) -> Signer<Wallet<N>> {
|
|
||||||
Signer {
|
|
||||||
inner: self,
|
|
||||||
provider: Some(provider),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: Network> FromStr for Wallet<N> {
|
|
||||||
type Err = secp256k1::Error;
|
|
||||||
|
|
||||||
fn from_str(src: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(PrivateKey::from_str(src)?.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<N: Network> From<PrivateKey> for Wallet<N> {
|
|
||||||
fn from(private_key: PrivateKey) -> Self {
|
|
||||||
let public_key = PublicKey::from(&private_key);
|
|
||||||
let address = Address::from(&private_key);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
private_key,
|
|
||||||
public_key,
|
|
||||||
address,
|
|
||||||
network: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Signer<'a, S> {
|
|
||||||
pub provider: Option<&'a Provider>,
|
|
||||||
pub inner: S,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum SignerError {
|
|
||||||
#[error(transparent)]
|
|
||||||
ClientError(#[from] ClientError),
|
|
||||||
#[error("no provider was found")]
|
|
||||||
NoProvider,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, N: Network> Signer<'a, Wallet<N>> {
|
|
||||||
/// Generates a random signer with no provider. Should be combined with the
|
|
||||||
/// `connect` method like `Signer::random(rng).connect(provider)`
|
|
||||||
pub fn random<R: Rng>(rng: &mut R) -> Self {
|
|
||||||
Signer {
|
|
||||||
provider: None,
|
|
||||||
inner: Wallet::new(rng),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send_transaction(
|
|
||||||
&self,
|
|
||||||
tx: UnsignedTransaction,
|
|
||||||
) -> Result<Transaction, SignerError> {
|
|
||||||
// TODO: Is there any nicer way to do this?
|
|
||||||
let provider = self.ensure_provider()?;
|
|
||||||
|
|
||||||
let signed_tx = self.sign_transaction(tx.clone());
|
|
||||||
|
|
||||||
provider.send_raw_transaction(&signed_tx.rlp()).await?;
|
|
||||||
|
|
||||||
Ok(signed_tx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, S> Signer<'a, S> {
|
|
||||||
/// Sets the provider for the signer
|
|
||||||
pub fn connect(mut self, provider: &'a Provider) -> Self {
|
|
||||||
self.provider = Some(provider);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ensure_provider(&self) -> Result<&Provider, SignerError> {
|
|
||||||
if let Some(provider) = self.provider {
|
|
||||||
Ok(provider)
|
|
||||||
} else {
|
|
||||||
Err(SignerError::NoProvider)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait SignerC {
|
|
||||||
/// Signs the hash of the provided message after prefixing it
|
|
||||||
fn sign_message<S: AsRef<[u8]>>(&self, message: S) -> Signature;
|
|
||||||
fn sign_transaction(&self, message: UnsignedTransaction) -> Transaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, N: Network> SignerC for Signer<'a, Wallet<N>> {
|
|
||||||
fn sign_message<S: AsRef<[u8]>>(&self, message: S) -> Signature {
|
|
||||||
self.inner.private_key.sign(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sign_transaction(&self, tx: UnsignedTransaction) -> Transaction {
|
|
||||||
self.inner.private_key.sign_transaction(tx, N::CHAIN_ID)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::{
|
||||||
|
jsonrpc::ClientError,
|
||||||
|
providers::{Provider, ProviderTrait},
|
||||||
|
types::{Transaction, TxHash, UnsignedTransaction},
|
||||||
|
wallet::Signer,
|
||||||
|
};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Client<'a, S> {
|
||||||
|
pub(super) provider: &'a Provider,
|
||||||
|
pub signer: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum SignerError {
|
||||||
|
#[error(transparent)]
|
||||||
|
ClientError(#[from] ClientError),
|
||||||
|
#[error("no provider was found")]
|
||||||
|
NoProvider,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S: Signer> Client<'a, S> {
|
||||||
|
pub async fn send_transaction(
|
||||||
|
&self,
|
||||||
|
tx: UnsignedTransaction,
|
||||||
|
) -> Result<Transaction, SignerError> {
|
||||||
|
// sign the transaction
|
||||||
|
let signed_tx = self.signer.sign_transaction(tx.clone());
|
||||||
|
|
||||||
|
// broadcast it
|
||||||
|
self.provider.send_raw_transaction(&signed_tx.rlp()).await?;
|
||||||
|
|
||||||
|
Ok(signed_tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Forward all other calls to the provider
|
||||||
|
pub async fn get_transaction<T: Send + Sync + Into<TxHash>>(
|
||||||
|
&self,
|
||||||
|
hash: T,
|
||||||
|
) -> Result<Transaction, ClientError> {
|
||||||
|
self.provider.get_transaction(hash).await
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
mod networks;
|
||||||
|
pub use networks::instantiated::*;
|
||||||
|
use networks::Network;
|
||||||
|
|
||||||
|
mod wallet;
|
||||||
|
pub use wallet::Wallet;
|
||||||
|
|
||||||
|
mod client;
|
||||||
|
use client::Client;
|
||||||
|
|
||||||
|
use crate::types::{Signature, Transaction, UnsignedTransaction};
|
||||||
|
|
||||||
|
/// Trait for signing transactions and messages
|
||||||
|
///
|
||||||
|
/// Implement this trait to support different signing modes, e.g. Ledger, hosted etc.
|
||||||
|
pub trait Signer {
|
||||||
|
/// Signs the hash of the provided message after prefixing it
|
||||||
|
fn sign_message<S: AsRef<[u8]>>(&self, message: S) -> Signature;
|
||||||
|
|
||||||
|
/// Signs the transaction
|
||||||
|
fn sign_transaction(&self, message: UnsignedTransaction) -> Transaction;
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
//! Networks are used inside wallets to ensure type-safety across networks. That way
|
||||||
|
//! a transaction that is designed to work with testnet does not accidentally work
|
||||||
|
//! with mainnet because the URL was changed.
|
||||||
|
|
||||||
|
use crate::types::U64;
|
||||||
|
|
||||||
|
pub trait Network {
|
||||||
|
const CHAIN_ID: Option<U64>;
|
||||||
|
|
||||||
|
// TODO: Default providers? e.g. `mainnet.infura.io/XXX`?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Mainnet;
|
||||||
|
|
||||||
|
impl Network for Mainnet {
|
||||||
|
const CHAIN_ID: Option<U64> = Some(U64([1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// No EIP155
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct EIP155Disabled;
|
||||||
|
|
||||||
|
// EIP155 being disabled means no chainId will be used
|
||||||
|
impl Network for EIP155Disabled {
|
||||||
|
const CHAIN_ID: Option<U64> = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod instantiated {
|
||||||
|
use super::*;
|
||||||
|
use crate::wallet::Wallet;
|
||||||
|
|
||||||
|
pub type MainnetWallet = Wallet<Mainnet>;
|
||||||
|
pub type AnyWallet = Wallet<EIP155Disabled>;
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
use crate::{
|
||||||
|
providers::Provider,
|
||||||
|
types::{Address, PrivateKey, PublicKey, Signature, Transaction, UnsignedTransaction},
|
||||||
|
wallet::{Client, Network, Signer},
|
||||||
|
};
|
||||||
|
|
||||||
|
use rand::Rng;
|
||||||
|
use std::{marker::PhantomData, str::FromStr};
|
||||||
|
|
||||||
|
/// A keypair
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Wallet<N> {
|
||||||
|
pub private_key: PrivateKey,
|
||||||
|
pub public_key: PublicKey,
|
||||||
|
pub address: Address,
|
||||||
|
network: PhantomData<N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, N: Network> Signer for Wallet<N> {
|
||||||
|
fn sign_message<S: AsRef<[u8]>>(&self, message: S) -> Signature {
|
||||||
|
self.private_key.sign(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_transaction(&self, tx: UnsignedTransaction) -> Transaction {
|
||||||
|
self.private_key.sign_transaction(tx, N::CHAIN_ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Network> Wallet<N> {
|
||||||
|
// TODO: Add support for mnemonic and encrypted JSON
|
||||||
|
|
||||||
|
/// Creates a new random keypair seeded with the provided RNG
|
||||||
|
pub fn new<R: Rng>(rng: &mut R) -> Self {
|
||||||
|
let private_key = PrivateKey::new(rng);
|
||||||
|
let public_key = PublicKey::from(&private_key);
|
||||||
|
let address = Address::from(&private_key);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
private_key,
|
||||||
|
public_key,
|
||||||
|
address,
|
||||||
|
network: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connects to a provider and returns a signer
|
||||||
|
pub fn connect(self, provider: &Provider) -> Client<Wallet<N>> {
|
||||||
|
Client {
|
||||||
|
signer: self,
|
||||||
|
provider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Network> From<PrivateKey> for Wallet<N> {
|
||||||
|
fn from(private_key: PrivateKey) -> Self {
|
||||||
|
let public_key = PublicKey::from(&private_key);
|
||||||
|
let address = Address::from(&private_key);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
private_key,
|
||||||
|
public_key,
|
||||||
|
address,
|
||||||
|
network: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<N: Network> FromStr for Wallet<N> {
|
||||||
|
type Err = secp256k1::Error;
|
||||||
|
|
||||||
|
fn from_str(src: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(PrivateKey::from_str(src)?.into())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue