use crate::Client;
use crate::client::merkle_payments::MerklePaymentReceipt;
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 {
receipt_from_store_quotes_filtered("es, None)
}
pub fn receipt_from_store_quotes_filtered(
quotes: &StoreQuote,
paid_quote_hashes: Option<&std::collections::BTreeSet<ant_evm::QuoteHash>>,
) -> Receipt {
let mut receipt = Receipt::new();
for (content_addr, quote_for_address) in "es.0 {
let mut proof_of_payment = ClientProofOfPayment {
peer_quotes: vec![],
};
let mut price_sum = ant_evm::Amount::ZERO;
for (peer_id, addrs, quote, amount) in "e_for_address.0 {
if let Some(paid_hashes) = paid_quote_hashes
&& !paid_hashes.contains("e.hash())
{
continue;
}
proof_of_payment.peer_quotes.push((
EncodedPeerId::from(*peer_id),
addrs.0.clone(),
quote.clone(),
));
price_sum += *amount;
}
if proof_of_payment.peer_quotes.is_empty() {
continue;
}
let price = AttoTokens::from_atto(price_sum);
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)
}
}
#[derive(Clone)]
pub enum BulkPaymentOption {
Wallet(EvmWallet),
ForceMerkle(EvmWallet),
ForceRegular(EvmWallet),
Receipt(Receipt),
MerkleReceipt(MerklePaymentReceipt),
ContinueMerkle(EvmWallet, MerklePaymentReceipt),
}
impl From<EvmWallet> for BulkPaymentOption {
fn from(value: EvmWallet) -> Self {
BulkPaymentOption::Wallet(value)
}
}
impl From<&EvmWallet> for BulkPaymentOption {
fn from(value: &EvmWallet) -> Self {
BulkPaymentOption::Wallet(value.clone())
}
}
impl From<Receipt> for BulkPaymentOption {
fn from(value: Receipt) -> Self {
BulkPaymentOption::Receipt(value)
}
}
impl From<MerklePaymentReceipt> for BulkPaymentOption {
fn from(value: MerklePaymentReceipt) -> Self {
BulkPaymentOption::MerkleReceipt(value)
}
}
impl From<PaymentOption> for BulkPaymentOption {
fn from(value: PaymentOption) -> Self {
match value {
PaymentOption::Wallet(w) => BulkPaymentOption::Wallet(w),
PaymentOption::Receipt(r) => BulkPaymentOption::Receipt(r),
}
}
}
impl BulkPaymentOption {
pub fn wallet(&self) -> Option<&EvmWallet> {
match self {
BulkPaymentOption::Wallet(w) => Some(w),
BulkPaymentOption::ForceMerkle(w) => Some(w),
BulkPaymentOption::ForceRegular(w) => Some(w),
BulkPaymentOption::Receipt(_) => None,
BulkPaymentOption::MerkleReceipt(_) => None,
BulkPaymentOption::ContinueMerkle(w, _) => Some(w),
}
}
pub fn to_payment_option(&self) -> Option<PaymentOption> {
match self {
BulkPaymentOption::Wallet(w) => Some(PaymentOption::Wallet(w.clone())),
BulkPaymentOption::ForceMerkle(w) => Some(PaymentOption::Wallet(w.clone())),
BulkPaymentOption::ForceRegular(w) => Some(PaymentOption::Wallet(w.clone())),
BulkPaymentOption::Receipt(_) => None,
BulkPaymentOption::MerkleReceipt(_) => None,
BulkPaymentOption::ContinueMerkle(w, _) => Some(PaymentOption::Wallet(w.clone())),
}
}
pub fn is_force_merkle(&self) -> bool {
matches!(self, BulkPaymentOption::ForceMerkle(_))
}
pub fn is_force_regular(&self) -> bool {
matches!(self, BulkPaymentOption::ForceRegular(_))
}
}
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) => self.pay(data_type, content_addrs, &wallet).await,
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?;
crate::loud_info!("Paying for {} addresses..", quotes.len());
if !quotes.is_empty() {
debug!("Waiting for wallet lock");
let lock_guard = wallet.lock().await;
debug!("Locked wallet");
match wallet.pay_for_quotes(quotes.payments()).await {
Ok((_tx_hashes, gas_info)) => {
drop(lock_guard);
debug!("Unlocked wallet");
crate::loud_info!("Gas cost: {gas_info}");
}
Err(pay_err) => {
drop(lock_guard);
debug!("Unlocked wallet after payment error");
return Err(PayError::from(pay_err.0));
}
}
}
let skipped_chunks = number_of_content_addrs - quotes.len();
crate::loud_info!(
"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))
}
}