docs: update providers book and examples (#2098)
* fmt * add README.md * fix advanced_usage * add custom transport example and chapter * update Http * update providers examples Cargo.toml * update websockets * update ipc * update other providers * update providers index
This commit is contained in:
parent
91d88288a6
commit
b0ef6ee9a2
|
@ -1675,12 +1675,15 @@ dependencies = [
|
||||||
name = "examples-providers"
|
name = "examples-providers"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"ethers",
|
"ethers",
|
||||||
"eyre",
|
"eyre",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# The ethers-rs book
|
||||||
|
|
||||||
|
Everything about `ethers-rs`. Work-in-progress. View online here: <https://www.gakonst.com/ethers-rs>
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
The book is built with [mdbook](https://github.com/rust-lang/mdBook), which you can install by running `cargo install mdbook`.
|
||||||
|
|
||||||
|
To view changes live, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mdbook serve
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with docker:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker run -p 3000:3000 -v `pwd`:/book peaceiris/mdbook serve
|
||||||
|
```
|
||||||
|
|
||||||
|
To add a new section (file) to the book, add it to [`SUMMARY.md`](./SUMMARY.md).
|
|
@ -14,7 +14,7 @@
|
||||||
- [Quorum](./providers/quorum.md)
|
- [Quorum](./providers/quorum.md)
|
||||||
- [Retry](./providers/retry.md)
|
- [Retry](./providers/retry.md)
|
||||||
- [RW](./providers/rw.md)
|
- [RW](./providers/rw.md)
|
||||||
- [WebSocket](./providers/ws.md)
|
- [Custom](./providers/custom.md)
|
||||||
- [Advanced Usage](./providers/advanced_usage.md)
|
- [Advanced Usage](./providers/advanced_usage.md)
|
||||||
- [Middleware](./middleware/middleware.md)
|
- [Middleware](./middleware/middleware.md)
|
||||||
- [Builder](./middleware/builder.md)
|
- [Builder](./middleware/builder.md)
|
||||||
|
@ -88,4 +88,3 @@
|
||||||
- [Deploy contracts]()
|
- [Deploy contracts]()
|
||||||
- [Fork]()
|
- [Fork]()
|
||||||
- [Testing]()
|
- [Testing]()
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Connect to an Ethereum node
|
# Connect to an Ethereum node
|
||||||
|
|
||||||
Ethers-rs allows application to connect the blockchain using web3 providers. Providers act as an interface between applications and an Ethereum node, allowing you to send requests and receive responses via JSON-RPC messages.
|
Ethers-rs allows application to connect the blockchain using web3 providers. Providers act as an interface between applications and an Ethereum node, allowing you to send requests and receive responses via JSON-RPC messages.
|
||||||
|
|
||||||
Some common actions you can perform using a provider include:
|
Some common actions you can perform using a provider include:
|
||||||
|
@ -13,11 +14,12 @@ Some common actions you can perform using a provider include:
|
||||||
Providers are an important part of web3 libraries because they allow you to easily interact with the Ethereum blockchain without having to manage the underlying connection to the node yourself.
|
Providers are an important part of web3 libraries because they allow you to easily interact with the Ethereum blockchain without having to manage the underlying connection to the node yourself.
|
||||||
|
|
||||||
Code below shows a basic setup to connect a provider to a node:
|
Code below shows a basic setup to connect a provider to a node:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
/// The `prelude` module provides a convenient way to import a number
|
// The `prelude` module provides a convenient way to import a number
|
||||||
/// of common dependencies at once. This can be useful if you are working
|
// of common dependencies at once. This can be useful if you are working
|
||||||
/// with multiple parts of the library and want to avoid having
|
// with multiple parts of the library and want to avoid having
|
||||||
/// to import each dependency individually.
|
// to import each dependency individually.
|
||||||
use ethers::prelude::*;
|
use ethers::prelude::*;
|
||||||
|
|
||||||
const RPC_URL: &str = "https://mainnet.infura.io/v3/your-project-id";
|
const RPC_URL: &str = "https://mainnet.infura.io/v3/your-project-id";
|
||||||
|
@ -30,4 +32,4 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
# Intro
|
# Intro
|
||||||
|
|
||||||
Welcome to the hands-on guide for the ethers-rs library!
|
Welcome to the hands-on guide for the ethers-rs library!
|
||||||
|
|
||||||
This documentation contains a collection of examples demonstrating how to use the library to build Ethereum-based applications in Rust. The examples cover a range of topics, from basic smart contract interactions to more advanced usage of ethers-rs.
|
This documentation contains a collection of examples demonstrating how to use the library to build Ethereum-based applications in Rust. The examples cover a range of topics, from basic smart contract interactions to more advanced usage of ethers-rs.
|
||||||
|
|
||||||
```admonish info
|
```admonish info
|
||||||
You can find the official ethers-rs documentation on docs.rs - [here](https://docs.rs/ethers/0.5.0/ethers/).
|
You can find the official ethers-rs documentation on docs.rs - [here](https://docs.rs/ethers/0.5.0/ethers/).
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -11,12 +12,12 @@ Each example includes a detailed description of the functionality being demonstr
|
||||||
|
|
||||||
We hope that these docs will help you get started with ethers-rs and give you a better understanding of how to use the library to build your own web3 applications in Rust. If you have any questions or need further assistance, please don't hesitate to reach out to the ethers-rs community.
|
We hope that these docs will help you get started with ethers-rs and give you a better understanding of how to use the library to build your own web3 applications in Rust. If you have any questions or need further assistance, please don't hesitate to reach out to the ethers-rs community.
|
||||||
|
|
||||||
The following is a brief overview diagram of the topis covered in this guide.
|
The following is a brief overview diagram of the topics covered in this guide.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
{{#include ../mermaid-style.txt}}
|
{{#include ../mermaid-style.txt}}
|
||||||
|
|
||||||
graph LR
|
graph LR
|
||||||
A[Ethers-rs <br> Manual] --> A1[Providers]
|
A[Ethers-rs <br> Manual] --> A1[Providers]
|
||||||
A --> A2[Middleware]
|
A --> A2[Middleware]
|
||||||
A --> A3[Contracts]
|
A --> A3[Contracts]
|
||||||
|
@ -28,6 +29,7 @@ graph LR
|
||||||
A --> A9[Big numbers]
|
A --> A9[Big numbers]
|
||||||
A --> A10[Anvil]
|
A --> A10[Anvil]
|
||||||
```
|
```
|
||||||
```admonish bug
|
|
||||||
|
```admonish bug
|
||||||
This diagram is incomplete and will undergo continuous changes.
|
This diagram is incomplete and will undergo continuous changes.
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,25 +1,29 @@
|
||||||
# Start a new project
|
# Start a new project
|
||||||
To set up a new project with ethers-rs, you will need to install the Rust programming language and the cargo package manager on your system.
|
|
||||||
|
To set up a new project with ethers-rs, you will need to install the Rust programming language toolchain and the Cargo package manager on your system.
|
||||||
|
|
||||||
1. Install Rust by following the instructions at [https://www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install).
|
1. Install Rust by following the instructions at [https://www.rust-lang.org/tools/install](https://www.rust-lang.org/tools/install).
|
||||||
2. Once Rust is installed, create a new Rust project by running the following command:
|
2. Once Rust is installed, create a new Rust project by running the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo new my-project
|
cargo new my-project
|
||||||
```
|
```
|
||||||
This will create a new directory called my-project with the necessary files for a new Rust project.
|
|
||||||
|
This will create a new directory called my-project with the necessary files for a new Rust project.
|
||||||
|
|
||||||
3. Navigate to the project directory and add ethers-rs as a dependency in your `Cargo.toml` file:
|
3. Navigate to the project directory and add ethers-rs as a dependency in your `Cargo.toml` file:
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
ethers = "1.0.0"
|
|
||||||
|
|
||||||
# Most of ethers-rs features rely upon an async Rust runtime.
|
```toml
|
||||||
# Since Rust doesn't provide an async runtime itself, you can
|
[dependencies]
|
||||||
# include the excellent tokio library
|
ethers = "1.0.0"
|
||||||
tokio = { version = "1.23.0", features = ["macros"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to make experiments and/or play around with early ethers-rs features link our GitHub repo in the `Cargo.toml`.
|
# Most of ethers-rs features rely upon an async Rust runtime.
|
||||||
|
# Since Rust doesn't provide an async runtime itself, you can
|
||||||
|
# include the excellent tokio library
|
||||||
|
tokio = { version = "1.23.0", features = ["macros"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to make experiments and/or play around with early ethers-rs features link our GitHub repo in the `Cargo.toml`.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -33,33 +37,34 @@ To set up a new project with ethers-rs, you will need to install the Rust progra
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethers = { git = "https://github.com/gakonst/ethers-rs", rev = "1.0.2" }
|
ethers = { git = "https://github.com/gakonst/ethers-rs", rev = "1.0.2" }
|
||||||
```
|
```
|
||||||
> **Note:** using a Git repository as a dependency is generally not recommended
|
|
||||||
> for production projects, as it can make it difficult to ensure that you are using
|
> **Note:** using a Git repository as a dependency is generally not recommended
|
||||||
> a specific and stable version of the dependency.
|
> for production projects, as it can make it difficult to ensure that you are using
|
||||||
> It is usually better to specify a version number or range to ensure that your project
|
> a specific and stable version of the dependency.
|
||||||
> is reproducible.
|
> It is usually better to specify a version number or range to ensure that your project
|
||||||
|
> is reproducible.
|
||||||
|
|
||||||
## Enable transports
|
## Enable transports
|
||||||
|
|
||||||
Ethers-rs enables interactions with Ethereum nodes through different "transport" types, or communication protocols.
|
Ethers-rs enables interactions with Ethereum nodes through different "transport" types, or communication protocols.
|
||||||
The following transport types are currently supported by ethers.rs:
|
The following transport types are currently supported by ethers.rs:
|
||||||
|
|
||||||
* **HTTP(S):** The HTTP(S) transport is used to communicate with Ethereum nodes over the HTTP or HTTPS protocols. This is the most common way to interact with Ethereum nodes. If you are looking to connect to a HTTPS endpoint, then you need to enable the `rustls` or `openssl` features:
|
- **HTTP(S):** The HTTP(S) transport is used to communicate with Ethereum nodes over the HTTP or HTTPS protocols. This is the most common way to interact with Ethereum nodes. If you are looking to connect to a HTTPS endpoint, then you need to enable the `rustls` or `openssl` features:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethers = { version = "1.0.0", features = ["rustls"] }
|
ethers = { version = "1.0.0", features = ["rustls"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
* **WebSocket:** The WebSocket transport is used to communicate with Ethereum nodes over the WebSocket protocol, which is a widely-supported standard for establishing a bi-directional communication channel between a client and a server. This can be used for a variety of purposes, including receiving real-time updates from an Ethereum node, or submitting transactions to the Ethereum network. Websockets support is turned on via the feature-flag ws:
|
- **WebSocket:** The WebSocket transport is used to communicate with Ethereum nodes over the WebSocket protocol, which is a widely-supported standard for establishing a bi-directional communication channel between a client and a server. This can be used for a variety of purposes, including receiving real-time updates from an Ethereum node, or submitting transactions to the Ethereum network. Websockets support is turned on via the feature-flag ws:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethers = { version = "1.0.0", features = ["ws"] }
|
ethers = { version = "1.0.0", features = ["ws"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
* **IPC (Interprocess Communication):** The IPC transport is used to communicate with a local Ethereum node using the IPC protocol, which is a way for processes to communicate with each other on a single computer. This is commonly used in Ethereum development to allow applications to communicate with a local Ethereum node, such as geth or parity. IPC support is turned on via the feature-flag `ipc`:
|
- **IPC (Interprocess Communication):** The IPC transport is used to communicate with a local Ethereum node using the IPC protocol, which is a way for processes to communicate with each other on a single computer. This is commonly used in Ethereum development to allow applications to communicate with a local Ethereum node, such as geth or parity. IPC support is turned on via the feature-flag `ipc`:
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ethers = { version = "1.0.0", features = ["ipc"] }
|
ethers = { version = "1.0.0", features = ["ipc"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
# Advanced Usage
|
# Advanced Usage
|
||||||
|
|
||||||
|
|
||||||
## `CallBuilder`
|
## `CallBuilder`
|
||||||
|
|
||||||
The `CallBuilder` is an enum to help create complex calls. `CallBuilder` implements `[RawCall](https://docs.rs/ethers/latest/ethers/providers/call_raw/trait.RawCall.html)` methods for overriding parameters to the `eth_call`rpc method.
|
The `CallBuilder` is an enum to help create complex calls. `CallBuilder` implements [`RawCall`](https://docs.rs/ethers/latest/ethers/providers/call_raw/trait.RawCall.html) methods for overriding parameters to the `eth_call` rpc method.
|
||||||
|
|
||||||
Lets take a quick look at how to use the `CallBuilder`.
|
Lets take a quick look at how to use the `CallBuilder`.
|
||||||
|
|
||||||
|
@ -73,7 +72,6 @@ async fn main() -> eyre::Result<()> {
|
||||||
|
|
||||||
Let's look at how to use the state override set. In short, the state override set is an optional address-to-state mapping, where each entry specifies some state to be ephemerally overridden prior to executing the call. The state override set allows you to override an account's balance, an account's nonce, the code at a given address, the entire state of an account's storage or an individual slot in an account's storage. Note that the state override set is not a default feature and is not available on every node.
|
Let's look at how to use the state override set. In short, the state override set is an optional address-to-state mapping, where each entry specifies some state to be ephemerally overridden prior to executing the call. The state override set allows you to override an account's balance, an account's nonce, the code at a given address, the entire state of an account's storage or an individual slot in an account's storage. Note that the state override set is not a default feature and is not available on every node.
|
||||||
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use ethers::{
|
use ethers::{
|
||||||
providers::{
|
providers::{
|
||||||
|
@ -113,4 +111,4 @@ async fn main() -> eyre::Result<()> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, the account balance and nonce for the `from_adr` is overridden. The state override set is a very powerful tool that you can use to simulate complicated transactions without undergoing any actual state changes.
|
In this example, the account balance and nonce for the `from_adr` is overridden. The state override set is a very powerful tool that you can use to simulate complicated transactions without undergoing any actual state changes.
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Custom data transport
|
||||||
|
|
||||||
|
As [we've previously seen](./providers.md#data-transports), a transport must implement [`JsonRpcClient`](https://docs.rs/ethers/latest/ethers/providers/trait.JsonRpcClient.html), and can also optionally implement [`PubsubClient`](https://docs.rs/ethers/latest/ethers/providers/trait.PubsubClient.html).
|
||||||
|
|
||||||
|
Let's see how we can create a custom data transport by implementing one that stores either a `Ws` or an `Ipc` transport:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
{{#include ../../examples/providers/examples/custom.rs}}
|
||||||
|
```
|
|
@ -1,9 +1,10 @@
|
||||||
# Http
|
# Http
|
||||||
|
|
||||||
The `Http` provider establishes an Http connection with a node, allowing you to send RPC requests to the node to fetch data, simulate calls, send transactions and much more.
|
The `Http` provider establishes an HTTP connection with a node, allowing you to send RPC requests to the node to fetch data, simulate calls, send transactions and much more.
|
||||||
|
|
||||||
## Initializing an Http Provider
|
## Initializing an Http Provider
|
||||||
Lets take a quick look at few ways to create a new `Http` provider. Since the `Http` provider implements the [`FromStr`](https://doc.rust-lang.org/std/str/trait.FromStr.html) one of the easiest ways to initialize a new provider is by using the `from_str()` method.
|
|
||||||
|
Lets take a quick look at few ways to create a new `Http` provider. One of the easiest ways to initialize a new `Provider<Http>` is by using the [`TryFrom`](https://doc.rust-lang.org/stable/std/convert/trait.TryFrom.html) trait's `try_from` method.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use ethers::providers::{Http, Middleware, Provider};
|
use ethers::providers::{Http, Middleware, Provider};
|
||||||
|
@ -21,18 +22,20 @@ async fn main() -> eyre::Result<()> {
|
||||||
The `Http` provider also supplies a way to initialize a new authorized connection.
|
The `Http` provider also supplies a way to initialize a new authorized connection.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// Initializes a new HTTP Client with authentication
|
|
||||||
use ethers::providers::{Authorization, Http};
|
use ethers::providers::{Authorization, Http};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
|
// Initialize a new HTTP Client with authentication
|
||||||
let url = Url::parse("http://localhost:8545")?;
|
let url = Url::parse("http://localhost:8545")?;
|
||||||
let provider = Http::new_with_auth(url, Authorization::basic("admin", "good_password"));
|
let provider = Http::new_with_auth(url, Authorization::basic("admin", "good_password"));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Additionally, you can initialize a new provider with your own custom `reqwest::Client`.
|
Additionally, you can initialize a new provider with your own custom `reqwest::Client`.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use ethers::providers::Http;
|
use ethers::providers::Http;
|
||||||
|
@ -43,6 +46,7 @@ async fn main() -> eyre::Result<()> {
|
||||||
let url = Url::parse("http://localhost:8545")?;
|
let url = Url::parse("http://localhost:8545")?;
|
||||||
let client = reqwest::Client::builder().build()?;
|
let client = reqwest::Client::builder().build()?;
|
||||||
let provider = Http::new_with_client(url, client);
|
let provider = Http::new_with_client(url, client);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -67,16 +71,15 @@ async fn main() -> eyre::Result<()> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also use the provider to interact with smart contracts. The snippet below uses the provider to establish a new instance of a UniswapV2Pool and uses the `get_reserves()` method from the smart contract to fetch the current state of the pool's reserves.
|
You can also use the provider to interact with smart contracts. The snippet below uses the provider to establish a new instance of a UniswapV2Pool and uses the `get_reserves()` method from the smart contract to fetch the current state of the pool's reserves.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use std::{str::FromStr, sync::Arc};
|
|
||||||
|
|
||||||
use ethers::{
|
use ethers::{
|
||||||
prelude::abigen,
|
prelude::abigen,
|
||||||
providers::{Http, Provider},
|
providers::{Http, Provider},
|
||||||
types::H160,
|
types::Address,
|
||||||
};
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
abigen!(
|
abigen!(
|
||||||
IUniswapV2Pair,
|
IUniswapV2Pair,
|
||||||
|
@ -89,7 +92,7 @@ async fn main() -> eyre::Result<()> {
|
||||||
let provider = Arc::new(Provider::try_from(rpc_url)?);
|
let provider = Arc::new(Provider::try_from(rpc_url)?);
|
||||||
|
|
||||||
// Initialize a new instance of the Weth/Dai Uniswap V2 pair contract
|
// Initialize a new instance of the Weth/Dai Uniswap V2 pair contract
|
||||||
let pair_address = H160::from_str("0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11")?;
|
let pair_address: Address = "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".parse()?;
|
||||||
let uniswap_v2_pair = IUniswapV2Pair::new(pair_address, provider);
|
let uniswap_v2_pair = IUniswapV2Pair::new(pair_address, provider);
|
||||||
|
|
||||||
// Use the get_reserves() function to fetch the pool reserves
|
// Use the get_reserves() function to fetch the pool reserves
|
||||||
|
@ -100,9 +103,9 @@ async fn main() -> eyre::Result<()> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This example is a little more complicated, so lets walk through what is going on. The `IUniswapV2Pair` is a struct that is generated from the `abigen!()` macro. The `IUniswapV2Pair::new()` function is used to create a new instance of the contract, taking in an `Address` and an `Arc<M>` as arguments, where `M` is any type that implements the `Middleware` trait. Note that the provider is wrapped in an [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) when being passed into the `new()` function.
|
This example is a little more complicated, so let's walk through what is going on. The `IUniswapV2Pair` is a struct that is generated from the `abigen!()` macro. The `IUniswapV2Pair::new()` function is used to create a new instance of the contract, taking in an `Address` and an `Arc<M>` as arguments, where `M` is any type that implements the `Middleware` trait. Note that the provider is wrapped in an [`Arc`](https://doc.rust-lang.org/std/sync/struct.Arc.html) when being passed into the `new()` function.
|
||||||
|
|
||||||
It is very common to wrap a provider in an `Arc` to share the provider across threads. Lets look at another example where the provider is used asynchronously across two tokio threads. In the next example, a new provider is initialized and used to asynchronously fetch the number of Ommer blocks from the most recent block, as well as the previous block.
|
It is very common to wrap a provider in an `Arc` to share the provider across threads. Let's look at another example where the provider is used asynchronously across two tokio threads. In the next example, a new provider is initialized and used to asynchronously fetch the number of Ommer blocks from the most recent block, as well as the previous block.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -138,4 +141,4 @@ async fn main() -> eyre::Result<()> {
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Before heading to the next chapter, feel free to check out the docs for the [`Http` provider](https://docs.rs/ethers/latest/ethers/providers/struct.Http.html). Keep in mind that we will cover advanced usage of providers at the end of this chapter. Now that we have the basics covered, lets move on to the next Provider, Websockets!
|
Before heading to the next chapter, feel free to check out the docs for the [`Http` provider](https://docs.rs/ethers/latest/ethers/providers/struct.Http.html). Keep in mind that we will cover advanced usage of providers at the end of this chapter. Now that we have the basics covered, let's move on to the next provider, Websockets!
|
||||||
|
|
|
@ -1,80 +1,36 @@
|
||||||
# IPC provider
|
# IPC provider
|
||||||
The IPC (Inter-Process Communication) transport is a way for a process to communicate with a running Ethereum client over a local Unix domain socket. If you are new to IPC, you can [follow this link to learn more](https://en.wikipedia.org/wiki/Inter-process_communication). Using the IPC transport allows the ethers library to send JSON-RPC requests to the Ethereum client and receive responses, without the need for a network connection or HTTP server. This can be useful for interacting with a local Ethereum node that is running on the same machine. Using Ipc [is faster than RPC](https://github.com/0xKitsune/geth-ipc-rpc-bench), however you will need to have a local node that you can connect to.
|
|
||||||
|
The [IPC (Inter-Process Communication)](https://en.wikipedia.org/wiki/Inter-process_communication) transport allows our program to communicate with a node over a local [Unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket) or [Windows named pipe](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipes).
|
||||||
|
|
||||||
|
Using the IPC transport allows the ethers library to send JSON-RPC requests to the Ethereum client and receive responses, without the need for a network connection or HTTP server. This can be useful for interacting with a local Ethereum node that is running on the same network. Using IPC [is faster than RPC](https://github.com/0xKitsune/geth-ipc-rpc-bench), however you will need to have a local node that you can connect to.
|
||||||
|
|
||||||
## Initializing an Ipc Provider
|
## Initializing an Ipc Provider
|
||||||
Below is an example of how to initialize a new Ipc provider.
|
|
||||||
|
Below is an example of how to initialize a new Ipc provider.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use ethers::providers::Provider;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
|
// Using a UNIX domain socket: `/path/to/ipc`
|
||||||
// We instantiate the provider using the path of a local Unix domain socket
|
#[cfg(unix)]
|
||||||
// --------------------------------------------------------------------------------
|
|
||||||
// NOTE: The IPC transport supports push notifications, but we still need to specify a polling
|
|
||||||
// interval because only subscribe RPC calls (e.g., transactions, blocks, events) support push
|
|
||||||
// notifications in Ethereum's RPC API. For other calls we must use repeated polling for many
|
|
||||||
// operations even with the IPC transport.
|
|
||||||
let provider = Provider::connect_ipc("~/.ethereum/geth.ipc").await?;
|
let provider = Provider::connect_ipc("~/.ethereum/geth.ipc").await?;
|
||||||
|
|
||||||
|
// Using a Windows named pipe: `\\<machine_address>\pipe\<pipe_name>`
|
||||||
|
#[cfg(windows)]
|
||||||
|
let provider = Provider::connect_ipc(r"\\.\pipe\geth").await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that if you are using Windows, you must use [Windows Ipc (Named pipes)](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipes). Instead of passing the provider the path to the `.ipc` file, you must pass a named pipe (`\\<machine_address>\pipe\<pipe_name>`). For a local geth connection, the named pipe will look something like this: `\\.\pipe\geth`
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The `Ipc` provider has the same methods as the `Ws` provider, allowing it to subscribe and unsubscribe via a `NotificationStream`.
|
The `Ipc` provider implements both `JsonRpcClient` and `PubsubClient`, just like `Ws`.
|
||||||
|
|
||||||
|
In this example, we monitor the [`WETH/USDC`](https://etherscan.io/address/0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc) [UniswapV2](https://docs.uniswap.org/) pair reserves and print when they have changed.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use ethers::providers::{Middleware, Provider, StreamExt, Ws};
|
{{#include ../../examples/providers/examples/ipc.rs}}
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> eyre::Result<()> {
|
|
||||||
let provider = Provider::connect_ipc("~/.ethereum/geth.ipc").await?;
|
|
||||||
|
|
||||||
// Create a new stream yielding pending transactions from the mempool
|
|
||||||
let mut tx_pool_stream = provider.subscribe_pending_txs().await?;
|
|
||||||
|
|
||||||
while let Some(tx_hash) = tx_pool_stream.next().await {
|
|
||||||
println!("Pending tx: {:?}", tx_hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
Note that the `Ipc` provider, like all providers, has access to the methods defined by the `Middleware` trait. With this in mind, we can use the `Ipc` provider just like the `Http` provider as well, with the only difference being that we are connected to the node via a Unix socket now!
|
|
||||||
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use std::{str::FromStr, sync::Arc};
|
|
||||||
|
|
||||||
use ethers::{
|
|
||||||
prelude::abigen,
|
|
||||||
providers::{Http, Provider},
|
|
||||||
types::H160,
|
|
||||||
};
|
|
||||||
|
|
||||||
abigen!(
|
|
||||||
IUniswapV2Pair,
|
|
||||||
r#"[function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)]"#
|
|
||||||
);
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> eyre::Result<()> {
|
|
||||||
let provider = Provider::connect_ipc("~/.ethereum/geth.ipc").await?;
|
|
||||||
|
|
||||||
// Initialize a new instance of the Weth/Dai Uniswap V2 pair contract
|
|
||||||
let pair_address = H160::from_str("0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11")?;
|
|
||||||
let uniswap_v2_pair = IUniswapV2Pair::new(pair_address, provider);
|
|
||||||
|
|
||||||
// Use the get_reserves() function to fetch the pool reserves
|
|
||||||
let (reserve_0, reserve_1, block_timestamp_last) =
|
|
||||||
uniswap_v2_pair.get_reserves().call().await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
{{#include ../../examples/providers/examples/mock.rs}}
|
{{#include ../../examples/providers/examples/mock.rs}}
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,9 +1,22 @@
|
||||||
# Providers
|
# Providers
|
||||||
|
|
||||||
Providers play a central role in `ethers-rs`, enabling you to establish asynchronous [Ethereum JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) compliant clients.
|
A Provider is an abstraction of a connection to the Ethereum network, providing a concise, consistent interface to standard Ethereum node functionality.
|
||||||
|
|
||||||
Providers let your program connect to a node to get data, interact with smart contracts, listen to the mempool and much more. There are a few different types of default providers that are built into the library. The default providers are `Http`,`WS`,`Ipc`,`RWClient`,`Quorum`,`Mock` and `RetryClient`. In addition to all of these options, you can also create your own custom provider, which we will walk through later in this chapter. For now let take a look at what the `Provider` actually looks like.
|
This is achieved through the [`Middleware` trait][middleware], which provides the interface for the [Ethereum JSON-RPC API](https://ethereum.github.io/execution-apis/api-documentation) and other helpful methods, explained in more detail in [the Middleware chapter](../middleware/middleware.md), and the [`Provider`][provider] struct, which implements `Middleware`.
|
||||||
|
|
||||||
|
## Data transports
|
||||||
|
|
||||||
|
A [`Provider`][provider] wraps a generic data transport `P`, through which all JSON-RPC API calls are routed.
|
||||||
|
|
||||||
|
Ethers provides concrete transport implementations for [HTTP](./http.md), [WebSockets](./ws.md), and [IPC](./ipc.md), as well as higher level transports which wrap a single or multiple transports. Of course, it is also possible to [define custom data transports](./custom.md).
|
||||||
|
|
||||||
|
Transports implement the [`JsonRpcClient`](https://docs.rs/ethers/latest/ethers/providers/trait.JsonRpcClient.html) trait, which defines a `request` method, used for sending data to the underlying Ethereum node using [JSON-RPC](https://www.jsonrpc.org/specification).
|
||||||
|
|
||||||
|
Transports can optionally implement the [`PubsubClient`](https://docs.rs/ethers/latest/ethers/providers/trait.PubsubClient.html) trait, if they support the [Publish-subscribe pattern](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern), like `Websockets` and `IPC`. This is a [supertrait](https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-supertraits-to-require-one-traits-functionality-within-another-trait) of `JsonRpcClient`. It defines the `subscribe` and `unsubscribe` methods.
|
||||||
|
|
||||||
|
## The Provider type
|
||||||
|
|
||||||
|
This is the definition of the [`Provider`][provider] type:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -12,18 +25,17 @@ pub struct Provider<P> {
|
||||||
ens: Option<Address>,
|
ens: Option<Address>,
|
||||||
interval: Option<Duration>,
|
interval: Option<Duration>,
|
||||||
from: Option<Address>,
|
from: Option<Address>,
|
||||||
/// Node client hasn't been checked yet= `None`
|
node_client: Arc<Mutex<Option<NodeClient>>>,
|
||||||
/// Unsupported node client = `Some(None)`
|
|
||||||
/// Supported node client = `Some(Some(NodeClient))`
|
|
||||||
_node_client: Arc<Mutex<Option<NodeClient>>>,
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- `inner`: stores the generic data transport, which sends the requests;
|
||||||
The `Provider` struct defines a generic type `P` that can be any type that implements the [`JsonRpcClient` trait](https://docs.rs/ethers/latest/ethers/providers/trait.JsonRpcClient.html). The `inner` field stores the type that implements the `JsonRpcClient` type, allowing the Provider to make RPC calls to the node. The `ens` field is an optional value that specifies the ENS address for the provider's default sender. The `interval` field is an optional value that defines the polling interval when for streams (subscribing to logs, block headers, etc.). The `from` field is an optional type that allows you to set a default "from" address when constructing transactions and making calls. Lastly, the `_node_client` field is another optional value that allows the user to specify the node they are using to access node specific API calls.
|
- `ens`: optional override for the default ENS registry address;
|
||||||
|
- `interval`: optional value that defines the polling interval for `watch_*` streams;
|
||||||
|
- `from`: optional address that sets a default `from` address when constructing calls and transactions;
|
||||||
Note that all providers implement the [`Middleware` trait](https://docs.rs/ethers/latest/ethers/providers/trait.Middleware.html), which gives every provider access to [commonly used methods](https://docs.rs/ethers/latest/ethers/providers/struct.Provider.html#impl-Middleware-for-Provider%3CP%3E) to interact with the node. Later in this chapter, we will go over these methods and examples for how to use them in detail. Additionally, `Middleware` will be covered extensively in a later chapter.
|
- `node_client`: the type of node the provider is connected to, like Geth, Erigon, etc.
|
||||||
|
|
||||||
Now that you have a basis for what the `Provider` type actually is, the next few sections will walk through each implementation of the `Provider`, starting with the HTTP provider.
|
Now that you have a basis for what the `Provider` type actually is, the next few sections will walk through each implementation of the `Provider`, starting with the HTTP provider.
|
||||||
|
|
||||||
|
[middleware]: https://docs.rs/ethers/latest/ethers/providers/trait.Middleware.html
|
||||||
|
[provider]: https://docs.rs/ethers/latest/ethers/providers/struct.Provider.html
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
{{#include ../../examples/providers/examples/retry.rs}}
|
{{#include ../../examples/providers/examples/retry.rs}}
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
# WebSocket provider
|
# WebSocket provider
|
||||||
|
|
||||||
The Ws provider allows you to send JSON-RPC requests and receive responses over WebSocket connections. The WS provider can be used with any Ethereum node that supports WebSocket connections. This allows programs interact with the network in real-time without the need for HTTP polling for things like new block headers and filter logs. Ethers-rs has support for WebSockets via Tokio. Make sure that you have the “ws” and “rustls” / “openssl” features enabled in your project's toml file if you wish to use WebSockets.
|
The Ws provider allows you to send JSON-RPC requests and receive responses over WebSocket connections. The WS provider can be used with any Ethereum node that supports WebSocket connections. This allows programs interact with the network in real-time without the need for HTTP polling for things like new block headers and filter logs. Ethers-rs has support for WebSockets via Tokio. Make sure that you have the “ws” and “rustls” / “openssl” features enabled in your project's toml file if you wish to use WebSockets.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Initializing a WS Provider
|
## Initializing a WS Provider
|
||||||
|
|
||||||
Lets look at a few ways to create a new `WS` provider. Below is the most straightforward way to initialize a new `Ws` provider.
|
Lets look at a few ways to create a new `WS` provider. Below is the most straightforward way to initialize a new `Ws` provider.
|
||||||
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use ethers::providers::{Provider, Ws};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
let ws_endpoint = "";
|
let provider = Provider::<Ws>::connect("wss://...").await?;
|
||||||
let provider = Provider::<Ws>::connect(ws_endpoint).await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -19,66 +19,23 @@ async fn main() -> eyre::Result<()> {
|
||||||
Similar to the other providers, you can also establish an authorized connection with a node via websockets.
|
Similar to the other providers, you can also establish an authorized connection with a node via websockets.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use ethers::providers::{Authorization, Provider, Ws};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
let ws_endpoint = "";
|
let url = "wss://...";
|
||||||
let auth = Authorization::basic("username", "password");
|
let auth = Authorization::basic("username", "password");
|
||||||
|
let provider = Provider::<Ws>::connect_with_auth(url, auth).await?;
|
||||||
if let Ok(_provider) = Provider::<Ws>::connect_with_auth(url, auth).await {
|
|
||||||
println!("Create Ws provider with auth");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The `Ws` provider allows a user to send requests to the node just like the other providers. In addition to these methods, the `Ws` provider can also subscribe to new logs and events, watch transactions in the mempool and other types of data streams from the node. The default polling interval for the `Ws` provider is `7 seconds`. You can update the polling interval, by using the `provider.interval()` method.
|
The `Ws` provider allows a user to send requests to the node just like the other providers. In addition to these methods, the `Ws` provider can also subscribe to new logs and events, watch transactions in the mempool and other types of data streams from the node.
|
||||||
|
|
||||||
In the snippet below, a new `Ws` provider is used to watch pending transactions in the mempool as well as new block headers in two separate threads.
|
In the snippet below, a new `Ws` provider is used to subscribe to new pending transactions in the mempool as well as new block headers in two separate threads.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use ethers::providers::{Middleware, Provider, StreamExt, Ws};
|
{{#include ../../examples/providers/examples/ws.rs}}
|
||||||
use std::{sync::Arc, time::Duration};
|
```
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> eyre::Result<()> {
|
|
||||||
let ws_endpoint = "";
|
|
||||||
let mut provider = Provider::<Ws>::connect(ws_endpoint).await?;
|
|
||||||
|
|
||||||
// Update the polling interval
|
|
||||||
provider.set_interval(Duration::new(3, 0));
|
|
||||||
|
|
||||||
// Clone the providers to use in separate threads
|
|
||||||
let provider = Arc::new(provider);
|
|
||||||
let provider_0 = provider.clone();
|
|
||||||
let provider_1 = provider.clone();
|
|
||||||
|
|
||||||
let mut handles = vec![];
|
|
||||||
|
|
||||||
let pending_tx_handle = tokio::spawn(async move {
|
|
||||||
let mut tx_pool_stream = provider_0.watch_pending_transactions().await?;
|
|
||||||
while let Some(tx_hash) = tx_pool_stream.next().await {
|
|
||||||
println!("Pending tx: {:?}", tx_hash);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let new_block_headers_handle = tokio::spawn(async move {
|
|
||||||
let mut new_block_headers_stream = provider_1.watch_blocks().await?;
|
|
||||||
while let Some(block_hash) = new_block_headers_stream.next().await {
|
|
||||||
println!("New block: {:?}", block_hash);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add the JoinHandles to a vec and wait for the handles to complete
|
|
||||||
handles.push(pending_tx_handle);
|
|
||||||
handles.push(new_block_headers_handle);
|
|
||||||
for handle in handles {
|
|
||||||
if let Err(err) = handle.await {
|
|
||||||
panic!("{}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
|
@ -4,10 +4,6 @@ version = "1.0.2"
|
||||||
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
|
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["ipc"]
|
|
||||||
ipc = []
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ethers = { path = "../..", version = "1.0.0", features = ["abigen", "ipc", "rustls", "ws"] }
|
ethers = { path = "../..", version = "1.0.0", features = ["abigen", "ipc", "rustls", "ws"] }
|
||||||
|
|
||||||
|
@ -16,3 +12,7 @@ reqwest = { version = "0.11.14", default-features = false }
|
||||||
serde = { version = "1.0.144", features = ["derive"] }
|
serde = { version = "1.0.144", features = ["derive"] }
|
||||||
serde_json = "1.0.64"
|
serde_json = "1.0.64"
|
||||||
tokio = { version = "1.18", features = ["macros"] }
|
tokio = { version = "1.18", features = ["macros"] }
|
||||||
|
|
||||||
|
async-trait = "0.1"
|
||||||
|
url = "2.3"
|
||||||
|
thiserror = "1.0"
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
//! Create a custom data transport to use with a Provider.
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use ethers::prelude::*;
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use thiserror::Error;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// First we must create an error type, and implement [`From`] for [`ProviderError`].
|
||||||
|
///
|
||||||
|
/// Here we are using [`thiserror`](https://docs.rs/thiserror) to wrap [`WsClientError`]
|
||||||
|
/// and [`IpcError`].
|
||||||
|
/// This also provides a conversion implementation ([`From`]) for both, so we can use
|
||||||
|
/// the [question mark operator](https://doc.rust-lang.org/rust-by-example/std/result/question_mark.html)
|
||||||
|
/// later on in our implementations.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum WsOrIpcError {
|
||||||
|
#[error(transparent)]
|
||||||
|
Ws(#[from] WsClientError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Ipc(#[from] IpcError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<WsOrIpcError> for ProviderError {
|
||||||
|
fn from(value: WsOrIpcError) -> Self {
|
||||||
|
Self::JsonRpcClientError(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Next, we create our transport type, which in this case will be an enum that contains
|
||||||
|
/// either [`Ws`] or [`Ipc`].
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum WsOrIpc {
|
||||||
|
Ws(Ws),
|
||||||
|
Ipc(Ipc),
|
||||||
|
}
|
||||||
|
|
||||||
|
// We implement a convenience "constructor" method, to easily initialize the transport.
|
||||||
|
// This will connect to [`Ws`] if it's a valid [URL](url::Url), otherwise it'll
|
||||||
|
// default to [`Ipc`].
|
||||||
|
impl WsOrIpc {
|
||||||
|
pub async fn connect(s: &str) -> Result<Self, WsOrIpcError> {
|
||||||
|
let this = match Url::parse(s) {
|
||||||
|
Ok(url) => Self::Ws(Ws::connect(url).await?),
|
||||||
|
Err(_) => Self::Ipc(Ipc::connect(s).await?),
|
||||||
|
};
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, the most important step: implement [`JsonRpcClient`].
|
||||||
|
//
|
||||||
|
// For this implementation, we simply delegate to the wrapped transport and return the
|
||||||
|
// result.
|
||||||
|
//
|
||||||
|
// Note that we are using [`async-trait`](https://docs.rs/async-trait) for asynchronous
|
||||||
|
// functions in traits, as this is not yet supported in stable Rust; see:
|
||||||
|
// <https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html>
|
||||||
|
#[async_trait]
|
||||||
|
impl JsonRpcClient for WsOrIpc {
|
||||||
|
type Error = WsOrIpcError;
|
||||||
|
|
||||||
|
async fn request<T, R>(&self, method: &str, params: T) -> Result<R, Self::Error>
|
||||||
|
where
|
||||||
|
T: Debug + Serialize + Send + Sync,
|
||||||
|
R: DeserializeOwned + Send,
|
||||||
|
{
|
||||||
|
let res = match self {
|
||||||
|
Self::Ws(ws) => JsonRpcClient::request(ws, method, params).await?,
|
||||||
|
Self::Ipc(ipc) => JsonRpcClient::request(ipc, method, params).await?,
|
||||||
|
};
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can also implement [`PubsubClient`], since both `Ws` and `Ipc` implement it, by
|
||||||
|
// doing the same as in the `JsonRpcClient` implementation above.
|
||||||
|
impl PubsubClient for WsOrIpc {
|
||||||
|
// Since both `Ws` and `Ipc`'s `NotificationStream` associated type is the same,
|
||||||
|
// we can simply return one of them.
|
||||||
|
// In case they differed, we would have to create a `WsOrIpcNotificationStream`,
|
||||||
|
// similar to the error type.
|
||||||
|
type NotificationStream = <Ws as PubsubClient>::NotificationStream;
|
||||||
|
|
||||||
|
fn subscribe<T: Into<U256>>(&self, id: T) -> Result<Self::NotificationStream, Self::Error> {
|
||||||
|
let stream = match self {
|
||||||
|
Self::Ws(ws) => PubsubClient::subscribe(ws, id)?,
|
||||||
|
Self::Ipc(ipc) => PubsubClient::subscribe(ipc, id)?,
|
||||||
|
};
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unsubscribe<T: Into<U256>>(&self, id: T) -> Result<(), Self::Error> {
|
||||||
|
match self {
|
||||||
|
Self::Ws(ws) => PubsubClient::unsubscribe(ws, id)?,
|
||||||
|
Self::Ipc(ipc) => PubsubClient::unsubscribe(ipc, id)?,
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> eyre::Result<()> {
|
||||||
|
// Connect to our transport
|
||||||
|
let transport = WsOrIpc::connect("ws://localhost:8546").await?;
|
||||||
|
|
||||||
|
// Wrap the transport in a provider
|
||||||
|
let provider = Provider::new(transport);
|
||||||
|
|
||||||
|
// Now we can use our custom transport provider like normal
|
||||||
|
let block_number = provider.get_block_number().await?;
|
||||||
|
println!("Current block: {block_number}");
|
||||||
|
|
||||||
|
let mut subscription = provider.subscribe_blocks().await?;
|
||||||
|
while let Some(block) = subscription.next().await {
|
||||||
|
println!("New block: {:?}", block.number);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,13 +1,12 @@
|
||||||
|
//! The Http transport is used to send JSON-RPC requests over HTTP to an Ethereum node.
|
||||||
|
//! This is the most basic connection to a node.
|
||||||
|
|
||||||
use ethers::prelude::*;
|
use ethers::prelude::*;
|
||||||
use reqwest::header::{HeaderMap, HeaderValue};
|
use reqwest::header::{HeaderMap, HeaderValue};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
const RPC_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
|
const RPC_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
|
||||||
|
|
||||||
/// The Http transport is used to send JSON-RPC requests over Http to an
|
|
||||||
/// Ethereum node. It allows you to perform various actions on the Ethereum blockchain, such as
|
|
||||||
/// reading and writing data, sending transactions, and more. To use the Http transport, you will
|
|
||||||
/// need to create a new `Provider` instance as described in this example.
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
create_instance().await?;
|
create_instance().await?;
|
||||||
|
|
|
@ -1,32 +1,41 @@
|
||||||
/// The IPC (Inter-Process Communication) transport is a way for a process to communicate with a
|
//! The IPC (Inter-Process Communication) transport allows our program to communicate
|
||||||
/// running Ethereum client over a local Unix domain socket. Using the IPC transport allows the
|
//! with a node over a local [Unix domain socket](https://en.wikipedia.org/wiki/Unix_domain_socket)
|
||||||
/// ethers library to send JSON-RPC requests to the Ethereum client and receive responses, without
|
//! or [Windows named pipe](https://learn.microsoft.com/en-us/windows/win32/ipc/named-pipes).
|
||||||
/// the need for a network connection or HTTP server. This can be useful for interacting with a
|
//!
|
||||||
/// local Ethereum node that is running on the same machine.
|
//! It functions much the same as a Ws connection.
|
||||||
#[tokio::main]
|
|
||||||
#[cfg(feature = "ipc")]
|
|
||||||
async fn main() -> eyre::Result<()> {
|
|
||||||
use ethers::prelude::*;
|
|
||||||
|
|
||||||
// We instantiate the provider using the path of a local Unix domain socket
|
use ethers::prelude::*;
|
||||||
// --------------------------------------------------------------------------------
|
use std::sync::Arc;
|
||||||
// NOTE: The IPC transport supports push notifications, but we still need to specify a polling
|
|
||||||
// interval because only subscribe RPC calls (e.g., transactions, blocks, events) support push
|
abigen!(
|
||||||
// notifications in Ethereum's RPC API. For other calls we must use repeated polling for many
|
IUniswapV2Pair,
|
||||||
// operations even with the IPC transport.
|
"[function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)]"
|
||||||
let provider = Provider::connect_ipc("~/.ethereum/geth.ipc")
|
);
|
||||||
.await?
|
|
||||||
.interval(std::time::Duration::from_millis(2000));
|
#[tokio::main]
|
||||||
|
async fn main() -> eyre::Result<()> {
|
||||||
|
let provider = Provider::connect_ipc("~/.ethereum/geth.ipc").await?;
|
||||||
|
let provider = Arc::new(provider);
|
||||||
|
|
||||||
|
let pair_address: Address = "0xb4e16d0168e52d35cacd2c6185b44281ec28c9dc".parse()?;
|
||||||
|
let weth_usdc = IUniswapV2Pair::new(pair_address, provider.clone());
|
||||||
|
|
||||||
let block = provider.get_block_number().await?;
|
let block = provider.get_block_number().await?;
|
||||||
println!("Current block: {block}");
|
println!("Current block: {block}");
|
||||||
let mut stream = provider.watch_blocks().await?.stream();
|
|
||||||
|
let mut initial_reserves = weth_usdc.get_reserves().call().await?;
|
||||||
|
println!("Initial reserves: {initial_reserves:?}");
|
||||||
|
|
||||||
|
let mut stream = provider.subscribe_blocks().await?;
|
||||||
while let Some(block) = stream.next().await {
|
while let Some(block) = stream.next().await {
|
||||||
dbg!(block);
|
println!("New block: {:?}", block.number);
|
||||||
|
|
||||||
|
let reserves = weth_usdc.get_reserves().call().await?;
|
||||||
|
if reserves != initial_reserves {
|
||||||
|
println!("Reserves changed: old {initial_reserves:?} - new {reserves:?}");
|
||||||
|
initial_reserves = reserves;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "ipc"))]
|
|
||||||
fn main() {}
|
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
|
//! `MockProvider` is a mock Ethereum provider that can be used for testing purposes.
|
||||||
|
//! It allows to simulate Ethereum state and behavior, by explicitly instructing
|
||||||
|
//! provider's responses on client requests.
|
||||||
|
//!
|
||||||
|
//! This can be useful for testing code that relies on providers without the need to
|
||||||
|
//! connect to a real network or spend real Ether. It also allows to test code in a
|
||||||
|
//! deterministic manner, as you can control the state and behavior of the provider.
|
||||||
|
//!
|
||||||
|
//! In these examples we use the common Arrange, Act, Assert (AAA) test approach.
|
||||||
|
//! It is a useful pattern for well-structured, understandable and maintainable tests.
|
||||||
|
|
||||||
use ethers::prelude::*;
|
use ethers::prelude::*;
|
||||||
|
|
||||||
/// `MockProvider` is a mock Ethereum provider that can be used for testing purposes.
|
|
||||||
/// It allows to simulate Ethereum state and behavior, by explicitly instructing
|
|
||||||
/// provider's responses on client requests.
|
|
||||||
///
|
|
||||||
/// This can be useful for testing code that relies on providers without the need to
|
|
||||||
/// connect to a real network or spend real Ether. It also allows to test code in a
|
|
||||||
/// deterministic manner, as you can control the state and behavior of the provider.
|
|
||||||
///
|
|
||||||
/// In these examples we use the common Arrange, Act, Assert (AAA) test approach.
|
|
||||||
/// It is a useful pattern for well-structured, understandable and maintainable tests.
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
mocked_block_number().await?;
|
mocked_block_number().await?;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
//! Example usage for the `QuorumProvider` that requests multiple backends and only returns
|
//! The `QuorumProvider` sends a request to multiple backends and only returns a value
|
||||||
//! a value if the configured `Quorum` was reached.
|
//! if the configured `Quorum` was reached.
|
||||||
|
|
||||||
use ethers::{
|
use ethers::{
|
||||||
core::utils::Anvil,
|
core::utils::Anvil,
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
|
//! The RetryClient is a type that wraps around a JsonRpcClient and automatically retries failed
|
||||||
|
//! requests using an exponential backoff and filtering based on a RetryPolicy. It presents as a
|
||||||
|
//! JsonRpcClient, but with additional functionality for retrying requests.
|
||||||
|
//!
|
||||||
|
//! The RetryPolicy can be customized for specific applications and endpoints, mainly to handle
|
||||||
|
//! rate-limiting errors. In addition to the RetryPolicy, errors caused by connectivity issues such
|
||||||
|
//! as timed out connections or responses in the 5xx range can also be retried separately.
|
||||||
|
|
||||||
use ethers::prelude::*;
|
use ethers::prelude::*;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
const RPC_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
|
const RPC_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
|
||||||
|
|
||||||
/// The RetryClient is a type that wraps around a JsonRpcClient and automatically retries failed
|
|
||||||
/// requests using an exponential backoff and filtering based on a RetryPolicy. It presents as a
|
|
||||||
/// JsonRpcClient, but with additional functionality for retrying requests.
|
|
||||||
///
|
|
||||||
/// The RetryPolicy can be customized for specific applications and endpoints, mainly to handle
|
|
||||||
/// rate-limiting errors. In addition to the RetryPolicy, errors caused by connectivity issues such
|
|
||||||
/// as timed out connections or responses in the 5xx range can also be retried separately.
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
let provider = Http::new(Url::parse(RPC_URL)?);
|
let provider = Http::new(Url::parse(RPC_URL)?);
|
||||||
|
|
|
@ -1,23 +1,19 @@
|
||||||
//! Example usage for the `RwClient` that uses a didicated client to send transaction and nother one
|
//! The RwClient wraps two data transports: the first is used for read operations, and the second
|
||||||
//! for read ops
|
//! one is used for write operations, that consume gas like sending transactions.
|
||||||
|
|
||||||
use ethers::{
|
use ethers::{prelude::*, utils::Anvil};
|
||||||
core::utils::Anvil,
|
use url::Url;
|
||||||
providers::{Http, Middleware, Provider, Ws},
|
|
||||||
};
|
|
||||||
use eyre::Result;
|
|
||||||
use std::{str::FromStr, time::Duration};
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
let anvil = Anvil::new().spawn();
|
let anvil = Anvil::new().spawn();
|
||||||
|
|
||||||
let http = Http::from_str(&anvil.endpoint())?;
|
let http_url = Url::parse(&anvil.endpoint())?;
|
||||||
|
let http = Http::new(http_url);
|
||||||
|
|
||||||
let ws = Ws::connect(anvil.ws_endpoint()).await?;
|
let ws = Ws::connect(anvil.ws_endpoint()).await?;
|
||||||
|
|
||||||
let provider = Provider::rw(http, ws).interval(Duration::from_millis(10u64));
|
let _provider = Provider::rw(http, ws);
|
||||||
|
|
||||||
dbg!(provider.get_accounts().await?);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,48 +1,23 @@
|
||||||
use std::time::Duration;
|
//! The Ws transport allows you to send JSON-RPC requests and receive responses over
|
||||||
|
//! [WebSocket](https://en.wikipedia.org/wiki/WebSocket).
|
||||||
|
//!
|
||||||
|
//! This allows to interact with the network in real-time without the need for HTTP
|
||||||
|
//! polling.
|
||||||
|
|
||||||
use ethers::prelude::*;
|
use ethers::prelude::*;
|
||||||
|
|
||||||
const WSS_URL: &str = "wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27";
|
const WSS_URL: &str = "wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27";
|
||||||
|
|
||||||
/// The Ws transport allows you to send JSON-RPC requests and receive responses over WebSocket
|
|
||||||
/// connections. It is useful for connecting to Ethereum nodes that support WebSockets.
|
|
||||||
/// This allows to interact with the Ethereum network in real-time without the need for HTTP
|
|
||||||
/// polling.
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> eyre::Result<()> {
|
async fn main() -> eyre::Result<()> {
|
||||||
create_instance().await?;
|
// A Ws provider can be created from a ws(s) URI.
|
||||||
watch_blocks().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_instance() -> eyre::Result<()> {
|
|
||||||
// An Ws provider can be created from an ws(s) URI.
|
|
||||||
// In case of wss you must add the "rustls" or "openssl" feature
|
// In case of wss you must add the "rustls" or "openssl" feature
|
||||||
// to the ethers library dependency in `Cargo.toml`.
|
// to the ethers library dependency in `Cargo.toml`.
|
||||||
//------------------------------------------------------------------------------------------
|
|
||||||
// NOTE: The Ws transport supports push notifications, but we still need to specify a polling
|
|
||||||
// interval because only subscribe RPC calls (e.g., transactions, blocks, events) support push
|
|
||||||
// notifications in Ethereum's RPC API. For other calls we must use repeated polling for many
|
|
||||||
// operations even with the Ws transport.
|
|
||||||
let _provider = Provider::<Ws>::connect(WSS_URL).await?.interval(Duration::from_millis(500));
|
|
||||||
|
|
||||||
// Instantiate with auth to send basic authorization headers on connection.
|
|
||||||
let url = reqwest::Url::parse(WSS_URL)?;
|
|
||||||
let auth = Authorization::basic("username", "password");
|
|
||||||
if let Ok(_provider) = Provider::<Ws>::connect_with_auth(url, auth).await {
|
|
||||||
println!("Create Ws provider with auth");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Let's show how the Ws connection enables listening for blocks using a persistent TCP connection
|
|
||||||
async fn watch_blocks() -> eyre::Result<()> {
|
|
||||||
let provider = Provider::<Ws>::connect(WSS_URL).await?;
|
let provider = Provider::<Ws>::connect(WSS_URL).await?;
|
||||||
let mut stream = provider.watch_blocks().await?.take(1);
|
|
||||||
|
|
||||||
while let Some(block_hash) = stream.next().await {
|
let mut stream = provider.subscribe_blocks().await?.take(1);
|
||||||
println!("{block_hash:?}");
|
while let Some(block) = stream.next().await {
|
||||||
|
println!("{:?}", block.hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in New Issue