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