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.
|
||||
///
|
||||
|
@ -74,9 +79,7 @@ impl Ganache {
|
|||
let ganache_pid = cmd.spawn().expect("couldnt start ganache-cli");
|
||||
|
||||
// wait a couple of seconds for ganache to boot up
|
||||
// TODO: Change this to poll for `port`
|
||||
let sleep_time = std::time::Duration::from_secs(2);
|
||||
std::thread::sleep(sleep_time);
|
||||
std::thread::sleep(SLEEP_TIME);
|
||||
GanacheInstance(ganache_pid)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -282,8 +282,12 @@ impl<P: JsonRpcClient> Provider<P> {
|
|||
}
|
||||
|
||||
/// Signs data using a specific account. This account needs to be unlocked.
|
||||
pub async fn sign(&self, data: &Bytes, from: &Address) -> Result<Signature, ProviderError> {
|
||||
let data = utils::serialize(data);
|
||||
pub async fn sign<T: Into<Bytes>>(
|
||||
&self,
|
||||
data: T,
|
||||
from: &Address,
|
||||
) -> Result<Signature, ProviderError> {
|
||||
let data = utils::serialize(&data.into());
|
||||
let from = utils::serialize(from);
|
||||
Ok(self
|
||||
.0
|
||||
|
|
|
@ -12,7 +12,7 @@ use thiserror::Error;
|
|||
#[derive(Clone, Debug)]
|
||||
/// 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
|
||||
/// 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.
|
||||
///
|
||||
/// # Example
|
||||
|
@ -24,29 +24,35 @@ use thiserror::Error;
|
|||
/// use std::convert::TryFrom;
|
||||
///
|
||||
/// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let mut client: Client<_, _> = Provider::<Http>::try_from("http://localhost:8545")
|
||||
/// .expect("could not instantiate HTTP Provider").into();
|
||||
/// let provider = Provider::<Http>::try_from("http://localhost:8545")
|
||||
/// .expect("could not instantiate HTTP Provider");
|
||||
///
|
||||
/// // since it derefs to `Provider`, we can just call any of the JSON-RPC API methods
|
||||
/// 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
|
||||
/// // By default, signing of messages and transactions is done locally
|
||||
/// // (transactions will be broadcast via the eth_sendRawTransaction API)
|
||||
/// let wallet: Wallet = "380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc"
|
||||
/// .parse()
|
||||
/// .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()
|
||||
/// .to("0xd8da6bf26964af9d7eed9e03e53415d37aa96045".parse::<Address>()?)
|
||||
/// .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(crate) provider: Provider<P>,
|
||||
pub(crate) signer: Option<S>,
|
||||
pub(crate) signer: S,
|
||||
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)]
|
||||
/// Error thrown when the client interacts with the blockchain
|
||||
pub enum ClientError {
|
||||
|
@ -96,18 +92,25 @@ where
|
|||
S: Signer,
|
||||
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
|
||||
/// the connected node's `eth_call` API.
|
||||
pub async fn sign_message<T: Into<Bytes>>(&self, msg: T) -> Result<Signature, ClientError> {
|
||||
let msg = msg.into();
|
||||
Ok(if let Some(ref signer) = self.signer {
|
||||
signer.sign_message(msg)
|
||||
} else {
|
||||
self.provider.sign(&msg, &self.address).await?
|
||||
})
|
||||
Ok(self.signer.sign_message(msg.into()))
|
||||
}
|
||||
|
||||
/// 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(
|
||||
&self,
|
||||
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
|
||||
self.fill_transaction(&mut tx, block).await?;
|
||||
|
||||
// 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
|
||||
self.provider.send_raw_transaction(&signed_tx).await?;
|
||||
|
@ -148,7 +143,10 @@ where
|
|||
tx: &mut TransactionRequest,
|
||||
block: Option<BlockNumber>,
|
||||
) -> Result<(), ClientError> {
|
||||
// set the `from` field
|
||||
if tx.from.is_none() {
|
||||
tx.from = Some(self.address());
|
||||
}
|
||||
|
||||
// will poll and await the futures concurrently
|
||||
let (gas_price, gas, nonce) = join!(
|
||||
|
@ -166,12 +164,9 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the client's address (or `address(0)` if no signer is set)
|
||||
/// Returns the client's address
|
||||
pub fn address(&self) -> Address {
|
||||
self.signer
|
||||
.as_ref()
|
||||
.map(|s| s.address())
|
||||
.unwrap_or_default()
|
||||
self.signer.address()
|
||||
}
|
||||
|
||||
/// Returns a reference to the client's provider
|
||||
|
@ -180,31 +175,23 @@ where
|
|||
}
|
||||
|
||||
/// Returns a reference to the client's signer
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `self.signer` is `None`
|
||||
pub fn signer_unchecked(&self) -> &S {
|
||||
self.signer.as_ref().expect("no signer is configured")
|
||||
pub fn signer(&self) -> &S {
|
||||
&self.signer
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
self.signer = Some(signer);
|
||||
self.signer = signer;
|
||||
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 {
|
||||
self.provider = provider;
|
||||
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`
|
||||
|
|
|
@ -67,7 +67,7 @@ impl Wallet {
|
|||
let address = self.address();
|
||||
Client {
|
||||
address,
|
||||
signer: Some(self),
|
||||
signer: self,
|
||||
provider,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue