2020-10-14 22:10:31 -04:00
|
|
|
use anyhow::Result;
|
2020-09-28 02:18:50 -04:00
|
|
|
use async_trait::async_trait;
|
2020-10-21 19:57:42 -04:00
|
|
|
use backoff::{backoff::Constant as ConstantBackoff, future::FutureOperation as _};
|
2020-11-04 01:06:17 -05:00
|
|
|
use futures::TryFutureExt;
|
|
|
|
use monero::{Address, Network, PrivateKey};
|
2020-10-19 21:18:27 -04:00
|
|
|
use monero_harness::rpc::wallet;
|
2020-11-04 04:16:54 -05:00
|
|
|
use std::time::Duration;
|
2020-09-29 01:36:50 -04:00
|
|
|
use xmr_btc::monero::{
|
2020-11-04 04:16:54 -05:00
|
|
|
Amount, CreateWalletForOutput, PrivateViewKey, PublicKey, PublicViewKey, Transfer,
|
|
|
|
WatchForTransfer,
|
2020-09-29 01:36:50 -04:00
|
|
|
};
|
2020-09-28 02:18:50 -04:00
|
|
|
|
2020-11-04 01:06:17 -05:00
|
|
|
pub struct Wallet {
|
|
|
|
pub inner: wallet::Client,
|
|
|
|
/// Secondary wallet which is only used to watch for the Monero lock
|
|
|
|
/// transaction without needing a transfer proof.
|
|
|
|
pub watch_only: wallet::Client,
|
|
|
|
}
|
2020-09-28 02:18:50 -04:00
|
|
|
|
2020-10-21 19:57:42 -04:00
|
|
|
impl Wallet {
|
|
|
|
/// Get the balance of the primary account.
|
|
|
|
pub async fn get_balance(&self) -> Result<Amount> {
|
2020-11-04 01:06:17 -05:00
|
|
|
let amount = self.inner.get_balance(0).await?;
|
2020-10-21 19:57:42 -04:00
|
|
|
|
|
|
|
Ok(Amount::from_piconero(amount))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-28 02:18:50 -04:00
|
|
|
#[async_trait]
|
2020-10-19 21:18:27 -04:00
|
|
|
impl Transfer for Wallet {
|
2020-09-28 02:18:50 -04:00
|
|
|
async fn transfer(
|
|
|
|
&self,
|
|
|
|
public_spend_key: PublicKey,
|
|
|
|
public_view_key: PublicViewKey,
|
|
|
|
amount: Amount,
|
2020-11-04 04:16:54 -05:00
|
|
|
) -> Result<Amount> {
|
2020-09-28 02:18:50 -04:00
|
|
|
let destination_address =
|
|
|
|
Address::standard(Network::Mainnet, public_spend_key, public_view_key.into());
|
|
|
|
|
|
|
|
let res = self
|
2020-11-04 01:06:17 -05:00
|
|
|
.inner
|
2020-10-19 21:18:27 -04:00
|
|
|
.transfer(0, amount.as_piconero(), &destination_address.to_string())
|
2020-09-28 02:18:50 -04:00
|
|
|
.await?;
|
|
|
|
|
2020-11-04 04:16:54 -05:00
|
|
|
Ok(Amount::from_piconero(res.fee))
|
2020-09-29 01:36:50 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[async_trait]
|
2020-10-19 21:18:27 -04:00
|
|
|
impl CreateWalletForOutput for Wallet {
|
2020-10-07 20:27:54 -04:00
|
|
|
async fn create_and_load_wallet_for_output(
|
2020-09-29 01:36:50 -04:00
|
|
|
&self,
|
|
|
|
private_spend_key: PrivateKey,
|
|
|
|
private_view_key: PrivateViewKey,
|
|
|
|
) -> Result<()> {
|
|
|
|
let public_spend_key = PublicKey::from_private_key(&private_spend_key);
|
|
|
|
let public_view_key = PublicKey::from_private_key(&private_view_key.into());
|
|
|
|
|
|
|
|
let address = Address::standard(Network::Mainnet, public_spend_key, public_view_key);
|
|
|
|
|
|
|
|
let _ = self
|
2020-11-04 01:06:17 -05:00
|
|
|
.inner
|
2020-09-29 01:36:50 -04:00
|
|
|
.generate_from_keys(
|
|
|
|
&address.to_string(),
|
2020-11-04 00:05:08 -05:00
|
|
|
Some(&private_spend_key.to_string()),
|
2020-09-29 01:36:50 -04:00
|
|
|
&PrivateKey::from(private_view_key).to_string(),
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
Ok(())
|
2020-09-28 02:18:50 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[async_trait]
|
2020-10-19 21:18:27 -04:00
|
|
|
impl WatchForTransfer for Wallet {
|
2020-10-14 22:10:31 -04:00
|
|
|
async fn watch_for_transfer(
|
2020-11-04 01:06:17 -05:00
|
|
|
&self,
|
|
|
|
address: Address,
|
|
|
|
expected_amount: Amount,
|
|
|
|
private_view_key: PrivateViewKey,
|
2020-11-04 04:16:54 -05:00
|
|
|
) {
|
2020-11-04 01:06:17 -05:00
|
|
|
let address = address.to_string();
|
|
|
|
let private_view_key = PrivateKey::from(private_view_key).to_string();
|
|
|
|
let load_address = || {
|
|
|
|
self.watch_only
|
|
|
|
.generate_from_keys(&address, None, &private_view_key)
|
|
|
|
.map_err(backoff::Error::Transient)
|
|
|
|
};
|
|
|
|
|
2020-11-04 04:16:54 -05:00
|
|
|
// QUESTION: Should we really retry every error?
|
2020-11-04 01:06:17 -05:00
|
|
|
load_address
|
|
|
|
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
|
|
|
.await
|
|
|
|
.expect("transient error is never returned");
|
|
|
|
|
2020-11-04 04:16:54 -05:00
|
|
|
// QUESTION: Should we retry this error at all?
|
2020-11-04 01:06:17 -05:00
|
|
|
let refresh = || self.watch_only.refresh().map_err(backoff::Error::Transient);
|
|
|
|
|
|
|
|
refresh
|
|
|
|
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
|
|
|
.await
|
|
|
|
.expect("transient error is never returned");
|
|
|
|
|
2020-11-04 04:16:54 -05:00
|
|
|
let check_balance = || async {
|
|
|
|
let balance = self
|
|
|
|
.watch_only
|
2020-11-04 01:06:17 -05:00
|
|
|
.get_balance(0)
|
2020-11-04 04:16:54 -05:00
|
|
|
.await
|
|
|
|
.map_err(|_| backoff::Error::Transient("io"))?;
|
|
|
|
let balance = Amount::from_piconero(balance);
|
|
|
|
|
|
|
|
if balance != expected_amount {
|
|
|
|
return Err(backoff::Error::Transient("insufficient funds"));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
2020-11-04 01:06:17 -05:00
|
|
|
};
|
|
|
|
|
2020-11-04 04:16:54 -05:00
|
|
|
check_balance
|
2020-11-04 01:06:17 -05:00
|
|
|
.retry(ConstantBackoff::new(Duration::from_secs(1)))
|
|
|
|
.await
|
|
|
|
.expect("transient error is never returned");
|
|
|
|
}
|
|
|
|
}
|