init
This commit is contained in:
commit
1a49a62a81
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "ethers"
|
||||
version = "0.1.0"
|
||||
authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
solc = { git = "https://github.com/paritytech/rust_solc "}
|
||||
ethereum-types = "0.9.2"
|
||||
url = "2.1.1"
|
||||
once_cell = "1.4.0"
|
||||
async-trait = "0.1.31"
|
||||
reqwest = { version = "0.10.4", features = ["json"] }
|
||||
serde = { version = "1.0.110", features = ["derive"] }
|
||||
serde_json = "1.0.53"
|
||||
thiserror = "1.0.19"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "0.2.21", features = ["macros"] }
|
|
@ -0,0 +1,44 @@
|
|||
Provider model
|
||||
|
||||
should be usable both for tests and for normal actions
|
||||
|
||||
|
||||
Features
|
||||
- [ ] Keep your private keys in your client, safe and sound
|
||||
- [ ] Import and export JSON wallets (Geth, Parity and crowdsale)
|
||||
- [ ] Import and export BIP 39 mnemonic phrases (12 word backup phrases) and HD Wallets (English, Italian, Japanese, Korean, Simplified Chinese, Traditional Chinese; more coming soon)
|
||||
- [ ] Meta-classes create JavaScript objects from any contract ABI, including ABIv2 and Human-Readable ABI
|
||||
- [ ] Connect to Ethereum nodes over JSON-RPC, INFURA, Etherscan, Alchemy, Cloudflare or MetaMask.
|
||||
- [ ] ENS names are first-class citizens; they can be used anywhere an Ethereum addresses can be used
|
||||
- [ ] Tiny (~88kb compressed; 284kb uncompressed)
|
||||
- [ ] Complete functionality for all your Ethereum needs
|
||||
- [ ] Extensive documentation
|
||||
- [ ] Large collection of test cases which are maintained and added to
|
||||
- [ ] Fully TypeScript ready, with definition files and full TypeScript source
|
||||
- [ ] MIT License (including ALL dependencies); completely open source to do with as you please
|
||||
- [ ] Compatible with ethers.js and Metamask web3 providers via WASM
|
||||
- Calls by default are made async -> provide a synchronous API
|
||||
|
||||
|
||||
- Provider
|
||||
- Signer
|
||||
- Contract
|
||||
- Choice of BigNumber library? Probably the ones in ethabi
|
||||
- Human readable ABI very important
|
||||
- https://docs-beta.ethers.io/getting-started/
|
||||
- Supports IN-EVM Testing -> SUPER fast tests which are ALSO typesafe
|
||||
- build.rs type safe methods
|
||||
|
||||
This library is inspired by the APIs of Riemann Ether and Ethers https://github.com/summa-tx/riemann-ether#development
|
||||
|
||||
- Listening to events via HTTP polls, while WS push/pulls -> look at rust-web3
|
||||
|
||||
- Async std futures 1.0 RPC calls for everything `rpc_impl!` using reqwest and mockito
|
||||
|
||||
https://github.com/ethers-io/ethers.js/blob/ethers-v5-beta/packages/contracts/src.ts/index.ts#L721
|
||||
|
||||
- Wallet
|
||||
|
||||
ethers::wallet::new().connect(provider)
|
||||
::get_default_provider()
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
use ethers::providers::{Provider, ProviderTrait};
|
||||
use ethers::wallet::Signer;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let provider =
|
||||
Provider::try_from("https://mainnet.infura.io/v3/4aebe67796c64b95ab20802677b7bb55")
|
||||
.unwrap();
|
||||
|
||||
let num = provider.get_block_number().await.unwrap();
|
||||
dbg!(num);
|
||||
|
||||
// let signer = Signer::random().connect(&provider);
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
//! Minimal 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 reqwest::{Client, Error as ReqwestError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// JSON-RPC 2.0 Client
|
||||
pub struct HttpClient {
|
||||
id: AtomicU64,
|
||||
client: Client,
|
||||
url: Url,
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>>(
|
||||
&self,
|
||||
method: &str,
|
||||
params: Option<T>,
|
||||
) -> Result<R, ClientError> {
|
||||
let next_id = self.id.load(Ordering::SeqCst) + 1;
|
||||
self.id.store(next_id, Ordering::SeqCst);
|
||||
|
||||
let payload = Request::new(next_id, method, params);
|
||||
|
||||
let res = self
|
||||
.client
|
||||
.post(self.url.as_ref())
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await?;
|
||||
let res = res.json::<Response<R>>().await?;
|
||||
|
||||
Ok(res.data.into_result()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ClientError {
|
||||
#[error(transparent)]
|
||||
ReqwestError(#[from] ReqwestError),
|
||||
#[error(transparent)]
|
||||
JsonRpcError(#[from] JsonRpcError),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Error)]
|
||||
/// A JSON-RPC 2.0 error
|
||||
pub struct JsonRpcError {
|
||||
/// The error code
|
||||
pub code: i64,
|
||||
/// The error message
|
||||
pub message: String,
|
||||
/// Additional data
|
||||
pub data: Option<Value>,
|
||||
}
|
||||
|
||||
impl fmt::Display for JsonRpcError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"(code: {}, message: {}, data: {:?})",
|
||||
self.code, self.message, self.data
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
/// A JSON-RPC request
|
||||
struct Request<'a, T> {
|
||||
id: u64,
|
||||
jsonrpc: &'a str,
|
||||
method: &'a str,
|
||||
params: Option<T>,
|
||||
}
|
||||
|
||||
impl<'a, T> Request<'a, T> {
|
||||
/// Creates a new JSON RPC request
|
||||
fn new(id: u64, method: &'a str, params: Option<T>) -> Self {
|
||||
Self {
|
||||
id,
|
||||
jsonrpc: "2.0",
|
||||
method,
|
||||
params,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct Response<T> {
|
||||
id: u64,
|
||||
jsonrpc: String,
|
||||
#[serde(flatten)]
|
||||
data: ResponseData<T>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
enum ResponseData<R> {
|
||||
Error { error: JsonRpcError },
|
||||
Success { result: R },
|
||||
}
|
||||
|
||||
impl<R> ResponseData<R> {
|
||||
/// Consume response and return value
|
||||
fn into_result(self) -> Result<R, JsonRpcError> {
|
||||
match self {
|
||||
ResponseData::Success { result } => Ok(result),
|
||||
ResponseData::Error { error } => Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn response() {
|
||||
let response: Response<u64> =
|
||||
serde_json::from_str(r#"{"jsonrpc": "2.0", "result": 19, "id": 1}"#).unwrap();
|
||||
assert_eq!(response.id, 1);
|
||||
assert_eq!(response.data.into_result().unwrap(), 19);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
//! ethers-rs
|
||||
//!
|
||||
//! ethers-rs is a port of [ethers-js](github.com/ethers-io/ethers.js) in Rust.
|
||||
|
||||
mod network;
|
||||
|
||||
pub mod providers;
|
||||
|
||||
pub mod wallet;
|
||||
|
||||
pub mod primitives;
|
||||
|
||||
mod jsonrpc;
|
||||
|
||||
/// Re-export solc
|
||||
pub use solc;
|
|
@ -0,0 +1,18 @@
|
|||
/// Parameters for instantiating a network
|
||||
use ethereum_types::Address;
|
||||
|
||||
trait Network {
|
||||
const NAME: &'static str;
|
||||
const CHAIN_ID: u32;
|
||||
const ENS: Option<Address>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Mainnet;
|
||||
|
||||
impl Network for Mainnet {
|
||||
const NAME: &'static str = "mainnet";
|
||||
const CHAIN_ID: u32 = 1;
|
||||
// TODO: Replace with ENS address
|
||||
const ENS: Option<Address> = None;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
/// A signature;
|
||||
pub struct Signature([u8; 65]);
|
|
@ -0,0 +1,50 @@
|
|||
use crate::jsonrpc::{ClientError, HttpClient};
|
||||
use async_trait::async_trait;
|
||||
use ethereum_types::U256;
|
||||
use std::convert::TryFrom;
|
||||
use url::{ParseError, Url};
|
||||
|
||||
/// An Ethereum JSON-RPC compatible backend
|
||||
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]
|
||||
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_trait]
|
||||
pub trait ProviderTrait {
|
||||
type Error;
|
||||
|
||||
async fn get_block_number(&self) -> Result<U256, Self::Error>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[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));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
use crate::{primitives::Signature, providers::Provider};
|
||||
|
||||
pub struct Signer<'a> {
|
||||
provider: Option<&'a Provider>,
|
||||
}
|
||||
|
||||
impl<'a> Signer<'a> {
|
||||
pub fn random() -> Self {
|
||||
Signer { provider: None }
|
||||
}
|
||||
|
||||
pub fn connect(mut self, provider: &'a Provider) -> Self {
|
||||
self.provider = Some(provider);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
trait SignerC {
|
||||
/// Connects to a provider
|
||||
fn connect<'a>(self, provider: &'a Provider) -> Self;
|
||||
|
||||
fn sign_message(message: &[u8]) -> Signature;
|
||||
}
|
Loading…
Reference in New Issue