wallet refactor
This commit is contained in:
parent
07daa1a891
commit
f82f8db57f
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
pub mod providers;
|
pub mod providers;
|
||||||
|
|
||||||
pub mod wallet;
|
pub mod signers;
|
||||||
|
|
||||||
/// Ethereum related datatypes
|
/// Ethereum related datatypes
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
@ -12,7 +12,4 @@ pub mod types;
|
||||||
/// Re-export solc for convenience
|
/// Re-export solc for convenience
|
||||||
pub use solc;
|
pub use solc;
|
||||||
|
|
||||||
/// JSON-RPC client
|
|
||||||
mod jsonrpc;
|
|
||||||
|
|
||||||
mod utils;
|
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)
|
//! 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 reqwest::{Client, Error as ReqwestError};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::fmt;
|
use std::{
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
convert::TryFrom,
|
||||||
|
fmt,
|
||||||
|
sync::atomic::{AtomicU64, Ordering},
|
||||||
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use url::Url;
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
/// JSON-RPC 2.0 Client
|
/// An HTTP Client
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HttpClient {
|
pub struct Provider {
|
||||||
id: AtomicU64,
|
id: AtomicU64,
|
||||||
client: Client,
|
client: Client,
|
||||||
url: Url,
|
url: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for HttpClient {
|
#[derive(Error, Debug)]
|
||||||
fn clone(&self) -> Self {
|
pub enum ClientError {
|
||||||
Self {
|
#[error(transparent)]
|
||||||
id: AtomicU64::new(0),
|
ReqwestError(#[from] ReqwestError),
|
||||||
client: self.client.clone(),
|
#[error(transparent)]
|
||||||
url: self.url.clone(),
|
JsonRpcError(#[from] JsonRpcError),
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpClient {
|
#[async_trait]
|
||||||
/// Initializes a new HTTP Client
|
impl JsonRpcClient for Provider {
|
||||||
pub fn new(url: impl Into<Url>) -> Self {
|
type Error = ClientError;
|
||||||
Self {
|
|
||||||
id: AtomicU64::new(0),
|
|
||||||
client: Client::new(),
|
|
||||||
url: url.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sends a POST request with the provided method and the params serialized as JSON
|
/// 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,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
params: Option<T>,
|
params: Option<T>,
|
||||||
|
@ -59,12 +58,33 @@ impl HttpClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
impl Provider {
|
||||||
pub enum ClientError {
|
/// Initializes a new HTTP Client
|
||||||
#[error(transparent)]
|
pub fn new(url: impl Into<Url>) -> Self {
|
||||||
ReqwestError(#[from] ReqwestError),
|
Self {
|
||||||
#[error(transparent)]
|
id: AtomicU64::new(0),
|
||||||
JsonRpcError(#[from] JsonRpcError),
|
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)]
|
#[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 {
|
pub mod instantiated {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::wallet::Wallet;
|
use crate::signers::Wallet;
|
||||||
|
|
||||||
pub type MainnetWallet = Wallet<Mainnet>;
|
pub type MainnetWallet = Wallet<Mainnet>;
|
||||||
pub type AnyWallet = Wallet<EIP155Disabled>;
|
pub type AnyWallet = Wallet<EIP155Disabled>;
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
providers::Provider,
|
providers::{JsonRpcClient, Provider},
|
||||||
|
signers::{Client, Network, Signer},
|
||||||
types::{Address, PrivateKey, PublicKey, Signature, Transaction, UnsignedTransaction},
|
types::{Address, PrivateKey, PublicKey, Signature, Transaction, UnsignedTransaction},
|
||||||
wallet::{Client, Network, Signer},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
@ -43,8 +43,8 @@ impl<N: Network> Wallet<N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connects to a provider and returns a signer
|
/// Connects to a provider and returns a client
|
||||||
pub fn connect(self, provider: &Provider) -> Client<Wallet<N>> {
|
pub fn connect<P: JsonRpcClient>(self, provider: &Provider<P>) -> Client<Wallet<N>, P> {
|
||||||
Client {
|
Client {
|
||||||
signer: self,
|
signer: self,
|
||||||
provider,
|
provider,
|
|
@ -12,7 +12,7 @@ pub use transaction::{Transaction, TransactionRequest, UnsignedTransaction};
|
||||||
mod keys;
|
mod keys;
|
||||||
pub use keys::{PrivateKey, PublicKey};
|
pub use keys::{PrivateKey, PublicKey};
|
||||||
|
|
||||||
pub mod signature;
|
mod signature;
|
||||||
pub use signature::Signature;
|
pub use signature::Signature;
|
||||||
|
|
||||||
mod bytes;
|
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