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