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