refactor
This commit is contained in:
parent
913b917620
commit
07daa1a891
|
@ -1,20 +1,21 @@
|
|||
use ethers::providers::{Provider, ProviderTrait};
|
||||
use ethers::types::{BlockNumber, UnsignedTransaction};
|
||||
use ethers::wallet::{Mainnet, Wallet};
|
||||
use ethers::wallet::MainnetWallet;
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), failure::Error> {
|
||||
let provider = Provider::try_from("http://localhost:8545")?;
|
||||
let signer = Wallet::<Mainnet>::from_str(
|
||||
let client = MainnetWallet::from_str(
|
||||
"d8ebe1e50cfea1f9961908d9df28e64bb163fee9ee48320361b2eb0a54974269",
|
||||
)?
|
||||
.connect(&provider);
|
||||
|
||||
let nonce = provider
|
||||
.get_transaction_count(signer.inner.address, Some(BlockNumber::Latest))
|
||||
.get_transaction_count(client.signer.address, None)
|
||||
.await?;
|
||||
|
||||
let tx = UnsignedTransaction {
|
||||
to: Some("986eE0C8B91A58e490Ee59718Cca41056Cf55f24".parse().unwrap()),
|
||||
gas: 21000.into(),
|
||||
|
@ -24,10 +25,9 @@ async fn main() -> Result<(), failure::Error> {
|
|||
nonce,
|
||||
};
|
||||
|
||||
let tx = signer.send_transaction(tx).await?;
|
||||
let tx = client.send_transaction(tx).await?;
|
||||
|
||||
dbg!(tx.hash);
|
||||
let tx = provider.get_transaction(tx.hash).await?;
|
||||
let tx = client.get_transaction(tx.hash).await?;
|
||||
|
||||
println!("{}", serde_json::to_string(&tx)?);
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ impl ProviderTrait for Provider {
|
|||
block: Option<BlockNumber>,
|
||||
) -> Result<U256, Self::Error> {
|
||||
let from = utils::serialize(&from);
|
||||
let block = utils::serialize(&block);
|
||||
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
|
||||
self.0
|
||||
.request("eth_getTransactionCount", Some(&[from, block]))
|
||||
.await
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use super::U64;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
/// Block Number
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
|
@ -13,8 +14,6 @@ pub enum BlockNumber {
|
|||
Number(U64),
|
||||
}
|
||||
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
impl<T: Into<U64>> From<T> for BlockNumber {
|
||||
fn from(num: T) -> Self {
|
||||
BlockNumber::Number(num.into())
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Various Ethereum Related Datatypes
|
||||
|
||||
// Re-export common ethereum datatypes with more specific names
|
||||
pub use ethereum_types::H256 as TxHash;
|
||||
pub use ethereum_types::{Address, H256, U256, U64};
|
||||
|
||||
mod transaction;
|
||||
|
@ -19,49 +20,3 @@ pub use bytes::Bytes;
|
|||
|
||||
mod block;
|
||||
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