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:
DaniPopes 2023-01-31 18:36:12 +01:00 committed by GitHub
parent 91d88288a6
commit b0ef6ee9a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 346 additions and 276 deletions

3
Cargo.lock generated
View File

@ -1675,12 +1675,15 @@ dependencies = [
name = "examples-providers"
version = "1.0.2"
dependencies = [
"async-trait",
"ethers",
"eyre",
"reqwest",
"serde",
"serde_json",
"thiserror",
"tokio",
"url",
]
[[package]]

21
book/README.md Normal file
View File

@ -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).

View File

@ -14,7 +14,7 @@
- [Quorum](./providers/quorum.md)
- [Retry](./providers/retry.md)
- [RW](./providers/rw.md)
- [WebSocket](./providers/ws.md)
- [Custom](./providers/custom.md)
- [Advanced Usage](./providers/advanced_usage.md)
- [Middleware](./middleware/middleware.md)
- [Builder](./middleware/builder.md)
@ -88,4 +88,3 @@
- [Deploy contracts]()
- [Fork]()
- [Testing]()

View File

@ -1,4 +1,5 @@
# 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.
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.
Code below shows a basic setup to connect a provider to a node:
```rust
/// The `prelude` module provides a convenient way to import a number
/// of common dependencies at once. This can be useful if you are working
/// with multiple parts of the library and want to avoid having
/// to import each dependency individually.
// The `prelude` module provides a convenient way to import a number
// of common dependencies at once. This can be useful if you are working
// with multiple parts of the library and want to avoid having
// to import each dependency individually.
use ethers::prelude::*;
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(())
}
```
```

View File

@ -1,9 +1,10 @@
# Intro
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.
```admonish info
```admonish info
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.
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
{{#include ../mermaid-style.txt}}
graph LR
graph LR
A[Ethers-rs <br> Manual] --> A1[Providers]
A --> A2[Middleware]
A --> A3[Contracts]
@ -28,6 +29,7 @@ graph LR
A --> A9[Big numbers]
A --> A10[Anvil]
```
```admonish bug
```admonish bug
This diagram is incomplete and will undergo continuous changes.
```

View File

@ -1,25 +1,29 @@
# 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).
2. Once Rust is installed, create a new Rust project by running the following command:
```bash
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:
```toml
[dependencies]
ethers = "1.0.0"
# 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"] }
```
```toml
[dependencies]
ethers = "1.0.0"
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
[dependencies]
@ -33,33 +37,34 @@ To set up a new project with ethers-rs, you will need to install the Rust progra
[dependencies]
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
> a specific and stable version of the dependency.
> It is usually better to specify a version number or range to ensure that your project
> is reproducible.
> **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
> a specific and stable version of the dependency.
> It is usually better to specify a version number or range to ensure that your project
> is reproducible.
## Enable transports
Ethers-rs enables interactions with Ethereum nodes through different "transport" types, or communication protocols.
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
[dependencies]
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
[dependencies]
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
[dependencies]
ethers = { version = "1.0.0", features = ["ipc"] }
```

View File

@ -1,9 +1,8 @@
# Advanced Usage
## `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`.
@ -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.
```rust
use ethers::{
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.

9
book/providers/custom.md Normal file
View File

@ -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}}
```

View File

@ -1,9 +1,10 @@
# 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
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
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.
```rust
// Initializes a new HTTP Client with authentication
use ethers::providers::{Authorization, Http};
use url::Url;
#[tokio::main]
async fn main() -> eyre::Result<()> {
// Initialize a new HTTP Client with authentication
let url = Url::parse("http://localhost:8545")?;
let provider = Http::new_with_auth(url, Authorization::basic("admin", "good_password"));
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
use ethers::providers::Http;
@ -43,6 +46,7 @@ async fn main() -> eyre::Result<()> {
let url = Url::parse("http://localhost:8545")?;
let client = reqwest::Client::builder().build()?;
let provider = Http::new_with_client(url, client);
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
use std::{str::FromStr, sync::Arc};
use ethers::{
prelude::abigen,
providers::{Http, Provider},
types::H160,
types::Address,
};
use std::sync::Arc;
abigen!(
IUniswapV2Pair,
@ -89,7 +92,7 @@ async fn main() -> eyre::Result<()> {
let provider = Arc::new(Provider::try_from(rpc_url)?);
// 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);
// 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
use std::sync::Arc;
@ -138,4 +141,4 @@ async fn main() -> eyre::Result<()> {
<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!

View File

@ -1,80 +1,36 @@
# 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
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
use ethers::providers::Provider;
#[tokio::main]
async fn main() -> eyre::Result<()> {
// We instantiate the provider using the path of a local Unix domain socket
// --------------------------------------------------------------------------------
// 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.
// Using a UNIX domain socket: `/path/to/ipc`
#[cfg(unix)]
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(())
}
```
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
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
use ethers::providers::{Middleware, Provider, StreamExt, Ws};
#[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(())
}
{{#include ../../examples/providers/examples/ipc.rs}}
```

View File

@ -2,4 +2,4 @@
```rust
{{#include ../../examples/providers/examples/mock.rs}}
```
```

View File

@ -1,9 +1,22 @@
# 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
#[derive(Clone, Debug)]
@ -12,18 +25,17 @@ pub struct Provider<P> {
ens: Option<Address>,
interval: Option<Duration>,
from: Option<Address>,
/// Node client hasn't been checked yet= `None`
/// Unsupported node client = `Some(None)`
/// Supported node client = `Some(Some(NodeClient))`
_node_client: Arc<Mutex<Option<NodeClient>>>,
node_client: Arc<Mutex<Option<NodeClient>>>,
}
```
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.
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.
- `inner`: stores the generic data transport, which sends the requests;
- `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;
- `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.
[middleware]: https://docs.rs/ethers/latest/ethers/providers/trait.Middleware.html
[provider]: https://docs.rs/ethers/latest/ethers/providers/struct.Provider.html

View File

@ -2,4 +2,4 @@
```rust
{{#include ../../examples/providers/examples/retry.rs}}
```
```

View File

@ -1,17 +1,17 @@
# 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.
## 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.
```rust
use ethers::providers::{Provider, Ws};
#[tokio::main]
async fn main() -> eyre::Result<()> {
let ws_endpoint = "";
let provider = Provider::<Ws>::connect(ws_endpoint).await?;
let provider = Provider::<Ws>::connect("wss://...").await?;
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.
```rust
use ethers::providers::{Authorization, Provider, Ws};
#[tokio::main]
async fn main() -> eyre::Result<()> {
let ws_endpoint = "";
let url = "wss://...";
let auth = Authorization::basic("username", "password");
if let Ok(_provider) = Provider::<Ws>::connect_with_auth(url, auth).await {
println!("Create Ws provider with auth");
}
let provider = Provider::<Ws>::connect_with_auth(url, auth).await?;
Ok(())
}
```
## 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
use ethers::providers::{Middleware, Provider, StreamExt, Ws};
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(())
}
```
{{#include ../../examples/providers/examples/ws.rs}}
```

View File

@ -4,10 +4,6 @@ version = "1.0.2"
authors = ["Andrea Simeoni <andreasimeoni84@gmail.com>"]
edition = "2021"
[features]
default = ["ipc"]
ipc = []
[dev-dependencies]
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_json = "1.0.64"
tokio = { version = "1.18", features = ["macros"] }
async-trait = "0.1"
url = "2.3"
thiserror = "1.0"

View File

@ -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(())
}

View File

@ -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 reqwest::header::{HeaderMap, HeaderValue};
use std::sync::Arc;
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]
async fn main() -> eyre::Result<()> {
create_instance().await?;

View File

@ -1,32 +1,41 @@
/// 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. 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.
#[tokio::main]
#[cfg(feature = "ipc")]
async fn main() -> eyre::Result<()> {
use ethers::prelude::*;
//! The IPC (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).
//!
//! It functions much the same as a Ws connection.
// We instantiate the provider using the path of a local Unix domain socket
// --------------------------------------------------------------------------------
// 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?
.interval(std::time::Duration::from_millis(2000));
use ethers::prelude::*;
use std::sync::Arc;
abigen!(
IUniswapV2Pair,
"[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?;
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?;
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 {
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(())
}
#[cfg(not(feature = "ipc"))]
fn main() {}

View File

@ -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::*;
/// `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]
async fn main() -> eyre::Result<()> {
mocked_block_number().await?;

View File

@ -1,5 +1,5 @@
//! Example usage for the `QuorumProvider` that requests multiple backends and only returns
//! a value if the configured `Quorum` was reached.
//! The `QuorumProvider` sends a request to multiple backends and only returns a value
//! if the configured `Quorum` was reached.
use ethers::{
core::utils::Anvil,

View File

@ -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 reqwest::Url;
use std::time::Duration;
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]
async fn main() -> eyre::Result<()> {
let provider = Http::new(Url::parse(RPC_URL)?);

View File

@ -1,23 +1,19 @@
//! Example usage for the `RwClient` that uses a didicated client to send transaction and nother one
//! for read ops
//! The RwClient wraps two data transports: the first is used for read operations, and the second
//! one is used for write operations, that consume gas like sending transactions.
use ethers::{
core::utils::Anvil,
providers::{Http, Middleware, Provider, Ws},
};
use eyre::Result;
use std::{str::FromStr, time::Duration};
use ethers::{prelude::*, utils::Anvil};
use url::Url;
#[tokio::main]
async fn main() -> Result<()> {
async fn main() -> eyre::Result<()> {
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 provider = Provider::rw(http, ws).interval(Duration::from_millis(10u64));
dbg!(provider.get_accounts().await?);
let _provider = Provider::rw(http, ws);
Ok(())
}

View File

@ -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::*;
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]
async fn main() -> eyre::Result<()> {
create_instance().await?;
watch_blocks().await?;
Ok(())
}
async fn create_instance() -> eyre::Result<()> {
// An Ws provider can be created from an ws(s) URI.
// A Ws provider can be created from a ws(s) URI.
// In case of wss you must add the "rustls" or "openssl" feature
// 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 mut stream = provider.watch_blocks().await?.take(1);
while let Some(block_hash) = stream.next().await {
println!("{block_hash:?}");
let mut stream = provider.subscribe_blocks().await?.take(1);
while let Some(block) = stream.next().await {
println!("{:?}", block.hash);
}
Ok(())