wallet refactor
This commit is contained in:
parent
07daa1a891
commit
f82f8db57f
|
@ -4,7 +4,7 @@
|
|||
|
||||
pub mod providers;
|
||||
|
||||
pub mod wallet;
|
||||
pub mod signers;
|
||||
|
||||
/// Ethereum related datatypes
|
||||
pub mod types;
|
||||
|
@ -12,7 +12,4 @@ pub mod types;
|
|||
/// Re-export solc for convenience
|
||||
pub use solc;
|
||||
|
||||
/// JSON-RPC client
|
||||
mod jsonrpc;
|
||||
|
||||
mod utils;
|
||||
|
|
125
src/providers.rs
125
src/providers.rs
|
@ -1,125 +0,0 @@
|
|||
use crate::{
|
||||
jsonrpc::{ClientError, HttpClient},
|
||||
types::{Address, BlockNumber, Bytes, Transaction, TransactionRequest, TxHash, U256},
|
||||
utils,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use std::convert::TryFrom;
|
||||
use url::{ParseError, Url};
|
||||
|
||||
/// An Ethereum JSON-RPC compatible backend
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Provider(HttpClient);
|
||||
|
||||
impl From<HttpClient> for Provider {
|
||||
fn from(src: HttpClient) -> Self {
|
||||
Self(src)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Provider {
|
||||
type Error = ParseError;
|
||||
|
||||
fn try_from(src: &str) -> Result<Self, Self::Error> {
|
||||
Ok(Provider(HttpClient::new(Url::parse(src)?)))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
// TODO: Figure out a way to re-use the arguments with various transports -> need a trait which has a
|
||||
// `request` method
|
||||
impl ProviderTrait for Provider {
|
||||
type Error = ClientError;
|
||||
|
||||
async fn get_block_number(&self) -> Result<U256, Self::Error> {
|
||||
self.0.request("eth_blockNumber", None::<()>).await
|
||||
}
|
||||
|
||||
async fn get_transaction<T: Send + Sync + Into<TxHash>>(
|
||||
&self,
|
||||
hash: T,
|
||||
) -> Result<Transaction, Self::Error> {
|
||||
let hash = hash.into();
|
||||
self.0.request("eth_getTransactionByHash", Some(hash)).await
|
||||
}
|
||||
|
||||
async fn send_transaction(&self, tx: TransactionRequest) -> Result<TxHash, Self::Error> {
|
||||
self.0.request("eth_sendTransaction", Some(vec![tx])).await
|
||||
}
|
||||
|
||||
async fn send_raw_transaction(&self, rlp: &Bytes) -> Result<TxHash, Self::Error> {
|
||||
let rlp = utils::serialize(&rlp);
|
||||
self.0.request("eth_sendRawTransaction", Some(rlp)).await
|
||||
}
|
||||
|
||||
async fn get_transaction_count(
|
||||
&self,
|
||||
from: Address,
|
||||
block: Option<BlockNumber>,
|
||||
) -> Result<U256, Self::Error> {
|
||||
let from = utils::serialize(&from);
|
||||
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
|
||||
self.0
|
||||
.request("eth_getTransactionCount", Some(&[from, block]))
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for providing backend services. Different implementations for this may be used for using
|
||||
/// indexers or using multiple providers at the same time
|
||||
#[async_trait]
|
||||
pub trait ProviderTrait {
|
||||
type Error;
|
||||
|
||||
async fn get_block_number(&self) -> Result<U256, Self::Error>;
|
||||
|
||||
/// Gets a transaction by it shash
|
||||
async fn get_transaction<T: Into<TxHash> + Send + Sync>(
|
||||
&self,
|
||||
tx_hash: T,
|
||||
) -> Result<Transaction, Self::Error>;
|
||||
|
||||
/// Sends a transaciton request to the node
|
||||
async fn send_transaction(&self, tx: TransactionRequest) -> Result<TxHash, Self::Error>;
|
||||
|
||||
/// Broadcasts an RLP encoded signed transaction
|
||||
async fn send_raw_transaction(&self, tx: &Bytes) -> Result<TxHash, Self::Error>;
|
||||
|
||||
async fn get_transaction_count(
|
||||
&self,
|
||||
from: Address,
|
||||
block: Option<BlockNumber>,
|
||||
) -> Result<U256, Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use ethereum_types::Address;
|
||||
use std::str::FromStr;
|
||||
|
||||
// TODO: Make a Ganache helper
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_balance() {
|
||||
let provider = Provider::try_from("http://localhost:8545").unwrap();
|
||||
let num = provider.get_block_number().await.unwrap();
|
||||
assert_eq!(num, U256::from(0));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn send_transaction() {
|
||||
let provider = Provider::try_from("http://localhost:8545").unwrap();
|
||||
let tx_req = TransactionRequest {
|
||||
from: Address::from_str("e98C5Abe55bD5478717BC67DcE404B8730672298").unwrap(),
|
||||
to: Some(Address::from_str("d5CB69Fb66809B7Ca203DAe8fB571DD291a86764").unwrap()),
|
||||
nonce: None,
|
||||
data: None,
|
||||
value: Some(1000.into()),
|
||||
gas_price: None,
|
||||
gas: None,
|
||||
};
|
||||
let tx_hash = provider.send_transaction(tx_req).await.unwrap();
|
||||
dbg!(tx_hash);
|
||||
}
|
||||
}
|
|
@ -1,43 +1,42 @@
|
|||
//! Minimal JSON-RPC 2.0 Client
|
||||
//! Minimal HTTP JSON-RPC 2.0 Client
|
||||
//! The request/response code is taken from [here](https://github.com/althea-net/guac_rs/blob/master/web3/src/jsonrpc)
|
||||
use crate::providers::JsonRpcClient;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use reqwest::{Client, Error as ReqwestError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
fmt,
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
use url::{ParseError, Url};
|
||||
|
||||
/// JSON-RPC 2.0 Client
|
||||
/// An HTTP Client
|
||||
#[derive(Debug)]
|
||||
pub struct HttpClient {
|
||||
pub struct Provider {
|
||||
id: AtomicU64,
|
||||
client: Client,
|
||||
url: Url,
|
||||
}
|
||||
|
||||
impl Clone for HttpClient {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: AtomicU64::new(0),
|
||||
client: self.client.clone(),
|
||||
url: self.url.clone(),
|
||||
}
|
||||
}
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ClientError {
|
||||
#[error(transparent)]
|
||||
ReqwestError(#[from] ReqwestError),
|
||||
#[error(transparent)]
|
||||
JsonRpcError(#[from] JsonRpcError),
|
||||
}
|
||||
|
||||
impl HttpClient {
|
||||
/// Initializes a new HTTP Client
|
||||
pub fn new(url: impl Into<Url>) -> Self {
|
||||
Self {
|
||||
id: AtomicU64::new(0),
|
||||
client: Client::new(),
|
||||
url: url.into(),
|
||||
}
|
||||
}
|
||||
#[async_trait]
|
||||
impl JsonRpcClient for Provider {
|
||||
type Error = ClientError;
|
||||
|
||||
/// Sends a POST request with the provided method and the params serialized as JSON
|
||||
pub async fn request<T: Serialize, R: for<'a> Deserialize<'a>>(
|
||||
/// over HTTP
|
||||
async fn request<T: Serialize + Send + Sync, R: for<'a> Deserialize<'a>>(
|
||||
&self,
|
||||
method: &str,
|
||||
params: Option<T>,
|
||||
|
@ -59,12 +58,33 @@ impl HttpClient {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ClientError {
|
||||
#[error(transparent)]
|
||||
ReqwestError(#[from] ReqwestError),
|
||||
#[error(transparent)]
|
||||
JsonRpcError(#[from] JsonRpcError),
|
||||
impl Provider {
|
||||
/// Initializes a new HTTP Client
|
||||
pub fn new(url: impl Into<Url>) -> Self {
|
||||
Self {
|
||||
id: AtomicU64::new(0),
|
||||
client: Client::new(),
|
||||
url: url.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Provider {
|
||||
type Error = ParseError;
|
||||
|
||||
fn try_from(src: &str) -> Result<Self, Self::Error> {
|
||||
Ok(Provider::new(Url::parse(src)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Provider {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
id: AtomicU64::new(0),
|
||||
client: self.client.clone(),
|
||||
url: self.url.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Error)]
|
|
@ -0,0 +1,72 @@
|
|||
//! Ethereum compatible providers
|
||||
//! Currently supported:
|
||||
//! - Raw HTTP POST requests
|
||||
//!
|
||||
//! TODO: WebSockets, multiple backends, popular APIs etc.
|
||||
mod http;
|
||||
|
||||
use crate::{
|
||||
types::{Address, BlockNumber, Bytes, Transaction, TransactionRequest, TxHash, U256},
|
||||
utils,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{error::Error, fmt::Debug};
|
||||
|
||||
/// An HTTP provider for interacting with an Ethereum-compatible blockchain
|
||||
pub type HttpProvider = Provider<http::Provider>;
|
||||
|
||||
#[async_trait]
|
||||
/// Implement this trait in order to plug in different backends
|
||||
pub trait JsonRpcClient: Debug {
|
||||
type Error: Error;
|
||||
|
||||
/// Sends a request with the provided method and the params serialized as JSON
|
||||
async fn request<T: Serialize + Send + Sync, R: for<'a> Deserialize<'a>>(
|
||||
&self,
|
||||
method: &str,
|
||||
params: Option<T>,
|
||||
) -> Result<R, Self::Error>;
|
||||
}
|
||||
|
||||
/// An abstract provider for interacting with the [Ethereum JSON RPC
|
||||
/// API](https://github.com/ethereum/wiki/wiki/JSON-RPC)
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Provider<P>(P);
|
||||
|
||||
// JSON RPC bindings
|
||||
impl<P: JsonRpcClient> Provider<P> {
|
||||
pub async fn get_block_number(&self) -> Result<U256, P::Error> {
|
||||
self.0.request("eth_blockNumber", None::<()>).await
|
||||
}
|
||||
|
||||
pub async fn get_transaction<T: Send + Sync + Into<TxHash>>(
|
||||
&self,
|
||||
hash: T,
|
||||
) -> Result<Transaction, P::Error> {
|
||||
let hash = hash.into();
|
||||
self.0.request("eth_getTransactionByHash", Some(hash)).await
|
||||
}
|
||||
|
||||
pub async fn send_transaction(&self, tx: TransactionRequest) -> Result<TxHash, P::Error> {
|
||||
self.0.request("eth_sendTransaction", Some(tx)).await
|
||||
}
|
||||
|
||||
pub async fn send_raw_transaction(&self, rlp: &Bytes) -> Result<TxHash, P::Error> {
|
||||
let rlp = utils::serialize(&rlp);
|
||||
self.0.request("eth_sendRawTransaction", Some(rlp)).await
|
||||
}
|
||||
|
||||
pub async fn get_transaction_count(
|
||||
&self,
|
||||
from: Address,
|
||||
block: Option<BlockNumber>,
|
||||
) -> Result<U256, P::Error> {
|
||||
let from = utils::serialize(&from);
|
||||
let block = utils::serialize(&block.unwrap_or(BlockNumber::Latest));
|
||||
self.0
|
||||
.request("eth_getTransactionCount", Some(&[from, block]))
|
||||
.await
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
use crate::{
|
||||
providers::{JsonRpcClient, Provider},
|
||||
signers::Signer,
|
||||
types::{Transaction, TxHash, UnsignedTransaction},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Client<'a, S, P> {
|
||||
pub(super) provider: &'a Provider<P>,
|
||||
pub signer: S,
|
||||
}
|
||||
|
||||
impl<'a, S: Signer, P: JsonRpcClient> Client<'a, S, P> {
|
||||
pub async fn send_transaction(&self, tx: UnsignedTransaction) -> Result<Transaction, P::Error> {
|
||||
// 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, P::Error> {
|
||||
self.provider.get_transaction(hash).await
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ impl Network for EIP155Disabled {
|
|||
|
||||
pub mod instantiated {
|
||||
use super::*;
|
||||
use crate::wallet::Wallet;
|
||||
use crate::signers::Wallet;
|
||||
|
||||
pub type MainnetWallet = Wallet<Mainnet>;
|
||||
pub type AnyWallet = Wallet<EIP155Disabled>;
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
providers::Provider,
|
||||
providers::{JsonRpcClient, Provider},
|
||||
signers::{Client, Network, Signer},
|
||||
types::{Address, PrivateKey, PublicKey, Signature, Transaction, UnsignedTransaction},
|
||||
wallet::{Client, Network, Signer},
|
||||
};
|
||||
|
||||
use rand::Rng;
|
||||
|
@ -43,8 +43,8 @@ impl<N: Network> Wallet<N> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Connects to a provider and returns a signer
|
||||
pub fn connect(self, provider: &Provider) -> Client<Wallet<N>> {
|
||||
/// Connects to a provider and returns a client
|
||||
pub fn connect<P: JsonRpcClient>(self, provider: &Provider<P>) -> Client<Wallet<N>, P> {
|
||||
Client {
|
||||
signer: self,
|
||||
provider,
|
|
@ -12,7 +12,7 @@ pub use transaction::{Transaction, TransactionRequest, UnsignedTransaction};
|
|||
mod keys;
|
||||
pub use keys::{PrivateKey, PublicKey};
|
||||
|
||||
pub mod signature;
|
||||
mod signature;
|
||||
pub use signature::Signature;
|
||||
|
||||
mod bytes;
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue