use crate::Client;
use crate::client::quote::{DataTypes, StoreQuote};
use ant_evm::{ClientProofOfPayment, EncodedPeerId, EvmWallet, EvmWalletError};
use std::collections::HashMap;
use xor_name::XorName;
use super::quote::CostError;
pub use crate::{Amount, AttoTokens};
pub type Receipt = HashMap<XorName, (ClientProofOfPayment, AttoTokens)>;
pub type AlreadyPaidAddressesCount = usize;
#[derive(Debug, thiserror::Error)]
pub enum PayError {
#[error(
"EVM wallet and client use different EVM networks. Please use the same network for both."
)]
EvmWalletNetworkMismatch,
#[error("Wallet error: {0:?}")]
EvmWalletError(#[from] EvmWalletError),
#[error("Failed to self-encrypt data.")]
SelfEncryption(#[from] crate::self_encryption::Error),
#[error("Cost error: {0:?}")]
Cost(#[from] CostError),
}
pub fn receipt_from_store_quotes(quotes: StoreQuote) -> Receipt {
let mut receipt = Receipt::new();
for (content_addr, quote_for_address) in quotes.0 {
let price = AttoTokens::from_atto(quote_for_address.price());
let mut proof_of_payment = ClientProofOfPayment {
peer_quotes: vec![],
};
for (peer_id, addrs, quote, _amount) in quote_for_address.0 {
proof_of_payment
.peer_quotes
.push((EncodedPeerId::from(peer_id), addrs.0, quote));
}
if proof_of_payment.peer_quotes.is_empty() {
continue;
}
receipt.insert(content_addr, (proof_of_payment, price));
}
receipt
}
#[derive(Clone)]
pub enum PaymentOption {
Wallet(EvmWallet),
Receipt(Receipt),
}
impl From<EvmWallet> for PaymentOption {
fn from(value: EvmWallet) -> Self {
PaymentOption::Wallet(value)
}
}
impl From<&EvmWallet> for PaymentOption {
fn from(value: &EvmWallet) -> Self {
PaymentOption::Wallet(value.clone())
}
}
impl From<Receipt> for PaymentOption {
fn from(value: Receipt) -> Self {
PaymentOption::Receipt(value)
}
}
impl Client {
pub(crate) async fn pay_for_content_addrs(
&self,
data_type: DataTypes,
content_addrs: impl Iterator<Item = (XorName, usize)> + Clone,
payment_option: PaymentOption,
) -> Result<(Receipt, AlreadyPaidAddressesCount), PayError> {
match payment_option {
PaymentOption::Wallet(wallet) => {
let (receipt, skipped) = self.pay(data_type, content_addrs, &wallet).await?;
Ok((receipt, skipped))
}
PaymentOption::Receipt(receipt) => Ok((receipt, 0)),
}
}
pub(crate) async fn pay(
&self,
data_type: DataTypes,
content_addrs: impl Iterator<Item = (XorName, usize)> + Clone,
wallet: &EvmWallet,
) -> Result<(Receipt, AlreadyPaidAddressesCount), PayError> {
if wallet.network() != self.evm_network() {
return Err(PayError::EvmWalletNetworkMismatch);
}
let number_of_content_addrs = content_addrs.clone().count();
let quotes = self.get_store_quotes(data_type, content_addrs).await?;
info!("Paying for {} addresses..", quotes.len());
#[cfg(feature = "loud")]
println!("Paying for {} addresses..", quotes.len());
if !quotes.is_empty() {
debug!("Waiting for wallet lock");
let lock_guard = wallet.lock().await;
debug!("Locked wallet");
let _payments = wallet
.pay_for_quotes(quotes.payments())
.await
.map_err(|err| PayError::from(err.0))?;
drop(lock_guard);
debug!("Unlocked wallet");
}
let skipped_chunks = number_of_content_addrs - quotes.len();
info!(
"Payments of {} address completed. {} address were free / already paid for",
quotes.len(),
skipped_chunks
);
#[cfg(feature = "loud")]
println!(
"Payments of {} address completed. {} address were free / already paid for",
quotes.len(),
skipped_chunks
);
let receipt = receipt_from_store_quotes(quotes);
Ok((receipt, skipped_chunks))
}
}