Skip to main content

ant_core/data/client/
payment.rs

1//! Payment orchestration for the Autonomi client.
2//!
3//! Connects quote collection, on-chain EVM payment, and proof serialization.
4//! Every PUT to the network requires a valid payment proof.
5
6use crate::data::client::quote::median_paid_quote_issuer;
7use crate::data::client::Client;
8use crate::data::error::{Error, Result};
9use ant_protocol::evm::{EncodedPeerId, ProofOfPayment, Wallet};
10use ant_protocol::payment::{serialize_single_node_proof, PaymentProof, SingleNodePayment};
11use ant_protocol::transport::{MultiAddr, PeerId};
12use std::sync::Arc;
13use tracing::{debug, info};
14
15impl Client {
16    /// Get the wallet, returning an error if not configured.
17    pub(crate) fn require_wallet(&self) -> Result<&Arc<Wallet>> {
18        self.wallet().ok_or_else(|| {
19            Error::Payment("Wallet not configured — call with_wallet() first".to_string())
20        })
21    }
22
23    /// Pay for storage and return the serialized payment proof bytes.
24    ///
25    /// This orchestrates the full payment flow:
26    /// 1. Collect `CLOSE_GROUP_SIZE` quotes plus ordered PUT targets
27    /// 2. Build `SingleNodePayment` using node-reported prices (median 3x, others 0)
28    /// 3. Pay on-chain via the wallet
29    /// 4. Serialize `PaymentProof` with transaction hashes
30    ///
31    /// # Errors
32    ///
33    /// Returns an error if the wallet is not set, quotes cannot be collected,
34    /// on-chain payment fails, or serialization fails.
35    /// Returns `(proof_bytes, put_targets)`. The peer list is the ordered PUT
36    /// target set from quote planning: it starts with peers expected to accept
37    /// the paid proof and can include non-quoted fallback peers beyond the
38    /// quoted close group.
39    pub async fn pay_for_storage(
40        &self,
41        address: &[u8; 32],
42        data_size: u64,
43        data_type: u32,
44    ) -> Result<(Vec<u8>, Vec<(PeerId, Vec<MultiAddr>)>)> {
45        // Wallet is required for the on-chain payment step (step 4 below).
46        // Check early so we don't waste time collecting quotes for a misconfigured client.
47        let wallet = self.require_wallet()?;
48
49        debug!("Collecting quotes for address {}", hex::encode(address));
50
51        // 1. Collect quotes from network
52        let quote_plan = self
53            .get_store_quote_plan(address, data_size, data_type)
54            .await?;
55        let quotes_with_peers = quote_plan.quotes;
56        let median_quote_issuer =
57            median_paid_quote_issuer(&quotes_with_peers).ok_or_else(|| {
58                Error::Payment(
59                    "Failed to select median quote issuer from witnessed quotes".to_string(),
60                )
61            })?;
62
63        // Capture the ordered PUT target set for replication by the caller.
64        // This can be wider than the peers that supplied the paid quotes.
65        let quoted_peers = quote_plan.put_peers;
66
67        // 2. Build peer_quotes for ProofOfPayment + quotes for SingleNodePayment.
68        // Use node-reported prices directly — no contract price fetch needed.
69        let mut peer_quotes = Vec::with_capacity(quotes_with_peers.len());
70        let mut quotes_for_payment = Vec::with_capacity(quotes_with_peers.len());
71
72        for (peer_id, _addrs, quote, price) in quotes_with_peers {
73            let encoded = peer_id_to_encoded(&peer_id)?;
74            peer_quotes.push((encoded, quote.clone()));
75            quotes_for_payment.push((quote, price));
76        }
77
78        // 3. Create SingleNodePayment (sorts by price, selects median)
79        let payment = SingleNodePayment::from_quotes(quotes_for_payment)
80            .map_err(|e| Error::Payment(format!("Failed to create payment: {e}")))?;
81
82        info!(
83            "Selected SNP median paid quote issuer {} for address {} (median price: {})",
84            median_quote_issuer.0,
85            hex::encode(address),
86            median_quote_issuer.1
87        );
88        info!("Payment total: {} atto", payment.total_amount());
89
90        // 4. Pay on-chain
91        let tx_hashes = payment
92            .pay(wallet)
93            .await
94            .map_err(|e| Error::Payment(format!("On-chain payment failed: {e}")))?;
95
96        info!(
97            "On-chain payment succeeded: {} transactions",
98            tx_hashes.len()
99        );
100
101        // 5. Build and serialize proof with version tag
102        let proof = PaymentProof {
103            proof_of_payment: ProofOfPayment { peer_quotes },
104            tx_hashes,
105        };
106
107        let proof_bytes = serialize_single_node_proof(&proof)
108            .map_err(|e| Error::Serialization(format!("Failed to serialize payment proof: {e}")))?;
109
110        Ok((proof_bytes, quoted_peers))
111    }
112
113    /// Approve the wallet to spend tokens on the payment vault contract.
114    ///
115    /// This must be called once before any payments can be made.
116    /// Approves `U256::MAX` (unlimited) spending.
117    ///
118    /// # Errors
119    ///
120    /// Returns an error if the wallet is not set or the approval transaction fails.
121    pub async fn approve_token_spend(&self) -> Result<()> {
122        let wallet = self.require_wallet()?;
123        let evm_network = self.require_evm_network()?;
124
125        let vault_address = evm_network.payment_vault_address();
126        wallet
127            .approve_to_spend_tokens(*vault_address, ant_protocol::evm::U256::MAX)
128            .await
129            .map_err(|e| Error::Payment(format!("Token approval failed: {e}")))?;
130        info!("Token spend approved for payment vault contract");
131
132        Ok(())
133    }
134}
135
136/// Convert an ant-node `PeerId` to an `EncodedPeerId` for payment proofs.
137pub(crate) fn peer_id_to_encoded(peer_id: &PeerId) -> Result<EncodedPeerId> {
138    Ok(EncodedPeerId::new(*peer_id.as_bytes()))
139}