fix(signers): make wallet non-optional
This commit is contained in:
parent
6156c4bf90
commit
469c0cb96b
|
@ -1,4 +1,9 @@
|
||||||
use std::process::{Child, Command};
|
use std::{
|
||||||
|
process::{Child, Command},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SLEEP_TIME: Duration = Duration::from_secs(3);
|
||||||
|
|
||||||
/// A ganache CLI instance. Will close the instance when dropped.
|
/// A ganache CLI instance. Will close the instance when dropped.
|
||||||
///
|
///
|
||||||
|
@ -74,9 +79,7 @@ impl Ganache {
|
||||||
let ganache_pid = cmd.spawn().expect("couldnt start ganache-cli");
|
let ganache_pid = cmd.spawn().expect("couldnt start ganache-cli");
|
||||||
|
|
||||||
// wait a couple of seconds for ganache to boot up
|
// wait a couple of seconds for ganache to boot up
|
||||||
// TODO: Change this to poll for `port`
|
std::thread::sleep(SLEEP_TIME);
|
||||||
let sleep_time = std::time::Duration::from_secs(2);
|
|
||||||
std::thread::sleep(sleep_time);
|
|
||||||
GanacheInstance(ganache_pid)
|
GanacheInstance(ganache_pid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -282,8 +282,12 @@ impl<P: JsonRpcClient> Provider<P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs data using a specific account. This account needs to be unlocked.
|
/// Signs data using a specific account. This account needs to be unlocked.
|
||||||
pub async fn sign(&self, data: &Bytes, from: &Address) -> Result<Signature, ProviderError> {
|
pub async fn sign<T: Into<Bytes>>(
|
||||||
let data = utils::serialize(data);
|
&self,
|
||||||
|
data: T,
|
||||||
|
from: &Address,
|
||||||
|
) -> Result<Signature, ProviderError> {
|
||||||
|
let data = utils::serialize(&data.into());
|
||||||
let from = utils::serialize(from);
|
let from = utils::serialize(from);
|
||||||
Ok(self
|
Ok(self
|
||||||
.0
|
.0
|
||||||
|
|
|
@ -12,7 +12,7 @@ use thiserror::Error;
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
/// A client provides an interface for signing and broadcasting locally signed transactions
|
/// A client provides an interface for signing and broadcasting locally signed transactions
|
||||||
/// It Derefs to [`Provider`], which allows interacting with the Ethereum JSON-RPC provider
|
/// It Derefs to [`Provider`], which allows interacting with the Ethereum JSON-RPC provider
|
||||||
/// via the same API. Sending transactions also supports using ENS as a receiver. If you will
|
/// via the same API. Sending transactions also supports using [ENS](https://ens.domains/) as a receiver. If you will
|
||||||
/// not be using a local signer, it is recommended to use a [`Provider`] instead.
|
/// not be using a local signer, it is recommended to use a [`Provider`] instead.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
@ -24,29 +24,35 @@ use thiserror::Error;
|
||||||
/// use std::convert::TryFrom;
|
/// use std::convert::TryFrom;
|
||||||
///
|
///
|
||||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut client: Client<_, _> = Provider::<Http>::try_from("http://localhost:8545")
|
/// let provider = Provider::<Http>::try_from("http://localhost:8545")
|
||||||
/// .expect("could not instantiate HTTP Provider").into();
|
/// .expect("could not instantiate HTTP Provider");
|
||||||
///
|
///
|
||||||
/// // since it derefs to `Provider`, we can just call any of the JSON-RPC API methods
|
/// // By default, signing of messages and transactions is done locally
|
||||||
/// let block = client.get_block(100u64).await?;
|
|
||||||
///
|
|
||||||
/// // calling `sign_message` and `send_transaction` will use the unlocked accounts
|
|
||||||
/// // on the node.
|
|
||||||
/// let signed_msg = client.sign_message(b"hello".to_vec()).await?;
|
|
||||||
///
|
|
||||||
/// let tx = TransactionRequest::pay("vitalik.eth", 100);
|
|
||||||
/// let tx_hash = client.send_transaction(tx, None).await?;
|
|
||||||
///
|
|
||||||
/// // if we set a signer, signing of messages and transactions will be done locally
|
|
||||||
/// // (transactions will be broadcast via the eth_sendRawTransaction API)
|
/// // (transactions will be broadcast via the eth_sendRawTransaction API)
|
||||||
/// let wallet: Wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
|
/// let wallet: Wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
|
||||||
/// .parse()
|
/// .parse()
|
||||||
/// .unwrap();
|
/// .unwrap();
|
||||||
///
|
///
|
||||||
/// let client = client.with_signer(wallet);
|
/// let mut client = Client::new(provider, wallet);
|
||||||
///
|
///
|
||||||
/// let signed_msg2 = client.sign_message(b"hello".to_vec()).await?;
|
/// // since it derefs to `Provider`, we can just call any of the JSON-RPC API methods
|
||||||
|
/// let block = client.get_block(100u64).await?;
|
||||||
///
|
///
|
||||||
|
/// // You can use the node's `eth_sign` and `eth_sendTransaction` calls by calling the
|
||||||
|
/// // internal provider's method.
|
||||||
|
/// let signed_msg = client.provider().sign(b"hello".to_vec(), &client.address()).await?;
|
||||||
|
///
|
||||||
|
/// let tx = TransactionRequest::pay("vitalik.eth", 100);
|
||||||
|
/// let tx_hash = client.send_transaction(tx, None).await?;
|
||||||
|
///
|
||||||
|
/// // You can connect with other wallets at runtime via the `with_signer` function
|
||||||
|
/// let wallet2: Wallet = "cd8c407233c0560f6de24bb2dc60a8b02335c959a1a17f749ce6c1ccf63d74a7"
|
||||||
|
/// .parse()
|
||||||
|
/// .unwrap();
|
||||||
|
///
|
||||||
|
/// let signed_msg2 = client.with_signer(wallet2).sign_message(b"hello".to_vec()).await?;
|
||||||
|
///
|
||||||
|
/// // This call will be made with `wallet2` since `with_signer` takes a mutable reference.
|
||||||
/// let tx2 = TransactionRequest::new()
|
/// let tx2 = TransactionRequest::new()
|
||||||
/// .to("0xd8da6bf26964af9d7eed9e03e53415d37aa96045".parse::<Address>()?)
|
/// .to("0xd8da6bf26964af9d7eed9e03e53415d37aa96045".parse::<Address>()?)
|
||||||
/// .value(200);
|
/// .value(200);
|
||||||
|
@ -57,23 +63,13 @@ use thiserror::Error;
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`Provider`](../ethers_providers/struct.Provider.html)
|
/// [`Provider`]: ../ethers_providers/struct.Provider.html
|
||||||
pub struct Client<P, S> {
|
pub struct Client<P, S> {
|
||||||
pub(crate) provider: Provider<P>,
|
pub(crate) provider: Provider<P>,
|
||||||
pub(crate) signer: Option<S>,
|
pub(crate) signer: S,
|
||||||
pub(crate) address: Address,
|
pub(crate) address: Address,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P, S> From<Provider<P>> for Client<P, S> {
|
|
||||||
fn from(provider: Provider<P>) -> Self {
|
|
||||||
Client {
|
|
||||||
provider,
|
|
||||||
signer: None,
|
|
||||||
address: Address::zero(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
/// Error thrown when the client interacts with the blockchain
|
/// Error thrown when the client interacts with the blockchain
|
||||||
pub enum ClientError {
|
pub enum ClientError {
|
||||||
|
@ -96,18 +92,25 @@ where
|
||||||
S: Signer,
|
S: Signer,
|
||||||
P: JsonRpcClient,
|
P: JsonRpcClient,
|
||||||
{
|
{
|
||||||
|
/// Creates a new client from the provider and signer.
|
||||||
|
pub fn new(provider: Provider<P>, signer: S) -> Self {
|
||||||
|
let address = signer.address();
|
||||||
|
Client {
|
||||||
|
provider,
|
||||||
|
signer,
|
||||||
|
address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Signs a message with the internal signer, or if none is present it will make a call to
|
/// Signs a message with the internal signer, or if none is present it will make a call to
|
||||||
/// the connected node's `eth_call` API.
|
/// the connected node's `eth_call` API.
|
||||||
pub async fn sign_message<T: Into<Bytes>>(&self, msg: T) -> Result<Signature, ClientError> {
|
pub async fn sign_message<T: Into<Bytes>>(&self, msg: T) -> Result<Signature, ClientError> {
|
||||||
let msg = msg.into();
|
Ok(self.signer.sign_message(msg.into()))
|
||||||
Ok(if let Some(ref signer) = self.signer {
|
|
||||||
signer.sign_message(msg)
|
|
||||||
} else {
|
|
||||||
self.provider.sign(&msg, &self.address).await?
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signs and broadcasts the transaction
|
/// Signs and broadcasts the transaction. The optional parameter `block` can be passed so that
|
||||||
|
/// gas cost and nonce calculations take it into account. For simple transactions this can be
|
||||||
|
/// left to `None`.
|
||||||
pub async fn send_transaction(
|
pub async fn send_transaction(
|
||||||
&self,
|
&self,
|
||||||
mut tx: TransactionRequest,
|
mut tx: TransactionRequest,
|
||||||
|
@ -123,19 +126,11 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there is no local signer, then the transaction should use the
|
|
||||||
// node's signer which should already be unlocked
|
|
||||||
let signer = if let Some(ref signer) = self.signer {
|
|
||||||
signer
|
|
||||||
} else {
|
|
||||||
return Ok(self.provider.send_transaction(tx).await?);
|
|
||||||
};
|
|
||||||
|
|
||||||
// fill any missing fields
|
// fill any missing fields
|
||||||
self.fill_transaction(&mut tx, block).await?;
|
self.fill_transaction(&mut tx, block).await?;
|
||||||
|
|
||||||
// sign the transaction with the network
|
// sign the transaction with the network
|
||||||
let signed_tx = signer.sign_transaction(tx).map_err(Into::into)?;
|
let signed_tx = self.signer.sign_transaction(tx).map_err(Into::into)?;
|
||||||
|
|
||||||
// broadcast it
|
// broadcast it
|
||||||
self.provider.send_raw_transaction(&signed_tx).await?;
|
self.provider.send_raw_transaction(&signed_tx).await?;
|
||||||
|
@ -148,7 +143,10 @@ where
|
||||||
tx: &mut TransactionRequest,
|
tx: &mut TransactionRequest,
|
||||||
block: Option<BlockNumber>,
|
block: Option<BlockNumber>,
|
||||||
) -> Result<(), ClientError> {
|
) -> Result<(), ClientError> {
|
||||||
tx.from = Some(self.address());
|
// set the `from` field
|
||||||
|
if tx.from.is_none() {
|
||||||
|
tx.from = Some(self.address());
|
||||||
|
}
|
||||||
|
|
||||||
// will poll and await the futures concurrently
|
// will poll and await the futures concurrently
|
||||||
let (gas_price, gas, nonce) = join!(
|
let (gas_price, gas, nonce) = join!(
|
||||||
|
@ -166,12 +164,9 @@ where
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the client's address (or `address(0)` if no signer is set)
|
/// Returns the client's address
|
||||||
pub fn address(&self) -> Address {
|
pub fn address(&self) -> Address {
|
||||||
self.signer
|
self.signer.address()
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.address())
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the client's provider
|
/// Returns a reference to the client's provider
|
||||||
|
@ -180,31 +175,23 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the client's signer
|
/// Returns a reference to the client's signer
|
||||||
///
|
pub fn signer(&self) -> &S {
|
||||||
/// # Panics
|
&self.signer
|
||||||
///
|
|
||||||
/// If `self.signer` is `None`
|
|
||||||
pub fn signer_unchecked(&self) -> &S {
|
|
||||||
self.signer.as_ref().expect("no signer is configured")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the signer
|
/// Sets the signer and returns a mutable reference to self so that it can be used in chained
|
||||||
|
/// calls.
|
||||||
pub fn with_signer(&mut self, signer: S) -> &mut Self {
|
pub fn with_signer(&mut self, signer: S) -> &mut Self {
|
||||||
self.signer = Some(signer);
|
self.signer = signer;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the provider
|
/// Sets the provider and returns a mutable reference to self so that it can be used in chained
|
||||||
|
/// calls.
|
||||||
pub fn with_provider(&mut self, provider: Provider<P>) -> &mut Self {
|
pub fn with_provider(&mut self, provider: Provider<P>) -> &mut Self {
|
||||||
self.provider = provider;
|
self.provider = provider;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the default account to be used with the `eth_sign` API calls
|
|
||||||
pub fn from(&mut self, address: Address) -> &mut Self {
|
|
||||||
self.address = address;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls the future if `item` is None, otherwise returns a `futures::ok`
|
/// Calls the future if `item` is None, otherwise returns a `futures::ok`
|
||||||
|
|
|
@ -67,7 +67,7 @@ impl Wallet {
|
||||||
let address = self.address();
|
let address = self.address();
|
||||||
Client {
|
Client {
|
||||||
address,
|
address,
|
||||||
signer: Some(self),
|
signer: self,
|
||||||
provider,
|
provider,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue