Skip to main content

evmlib/contract/payment_vault/
handler.rs

1use crate::common::{Address, Amount, Calldata, TxHash};
2use crate::contract::payment_vault::error::Error;
3use crate::contract::payment_vault::interface::IPaymentVault;
4use crate::contract::payment_vault::interface::IPaymentVault::IPaymentVaultInstance;
5use crate::merkle_batch_payment::PoolHash;
6use crate::retry::{GasInfo, TransactionError, send_transaction_with_retries};
7use crate::transaction_config::TransactionConfig;
8use alloy::network::{Network, TransactionResponse};
9use alloy::providers::Provider;
10use exponential_backoff::Backoff;
11use std::time::Duration;
12
13pub struct PaymentVaultHandler<P: Provider<N>, N: Network> {
14    pub contract: IPaymentVaultInstance<P, N>,
15}
16
17impl<P, N> PaymentVaultHandler<P, N>
18where
19    P: Provider<N>,
20    N: Network,
21{
22    /// Create a new PaymentVaultHandler instance from a (proxy) contract's address
23    pub fn new(contract_address: Address, provider: P) -> Self {
24        let contract = IPaymentVault::new(contract_address, provider);
25        Self { contract }
26    }
27
28    /// Set the provider
29    pub fn set_provider(&mut self, provider: P) {
30        let address = *self.contract.address();
31        self.contract = IPaymentVault::new(address, provider);
32    }
33
34    // ── Single-node (quote) payments ────────────────────────────────────
35
36    /// Pay for quotes.
37    /// Returns the transaction hash and gas information.
38    pub async fn pay_for_quotes<I: IntoIterator<Item: Into<IPaymentVault::DataPayment>>>(
39        &self,
40        data_payments: I,
41        transaction_config: &TransactionConfig,
42    ) -> Result<(TxHash, GasInfo), Error> {
43        debug!("Paying for quotes.");
44        let (calldata, to) = self.pay_for_quotes_calldata(data_payments)?;
45        send_transaction_with_retries(
46            self.contract.provider(),
47            calldata,
48            to,
49            "pay for quotes",
50            transaction_config,
51        )
52        .await
53        .map_err(Error::from)
54    }
55
56    /// Returns the pay for quotes transaction calldata.
57    pub fn pay_for_quotes_calldata<I: IntoIterator<Item: Into<IPaymentVault::DataPayment>>>(
58        &self,
59        data_payments: I,
60    ) -> Result<(Calldata, Address), Error> {
61        let data_payments: Vec<IPaymentVault::DataPayment> =
62            data_payments.into_iter().map(|item| item.into()).collect();
63
64        let calldata = self
65            .contract
66            .payForQuotes(data_payments)
67            .calldata()
68            .to_owned();
69
70        Ok((calldata, *self.contract.address()))
71    }
72
73    // ── Merkle batch payments ───────────────────────────────────────────
74
75    /// Pay for Merkle tree batch.
76    ///
77    /// Sends `payForMerkleTree` with unpacked `PoolCommitment` structs (candidates have price).
78    ///
79    /// # Returns
80    /// * Tuple of (winner pool hash, total amount paid, gas info)
81    pub async fn pay_for_merkle_tree<I, T>(
82        &self,
83        depth: u8,
84        pool_commitments: I,
85        merkle_payment_timestamp: u64,
86        transaction_config: &TransactionConfig,
87    ) -> Result<(PoolHash, Amount, GasInfo), Error>
88    where
89        I: IntoIterator<Item = T>,
90        T: Into<IPaymentVault::PoolCommitment>,
91    {
92        debug!("Paying for Merkle tree: depth={depth}, timestamp={merkle_payment_timestamp}");
93
94        let (calldata, to) =
95            self.pay_for_merkle_tree_calldata(depth, pool_commitments, merkle_payment_timestamp)?;
96
97        let (tx_hash, gas_info) = self
98            .send_transaction_and_handle_errors(calldata, to, transaction_config)
99            .await?;
100
101        let event = self.get_merkle_payment_event(tx_hash).await?;
102
103        let winner_pool_hash = event.winnerPoolHash.0;
104        let total_amount = event.totalAmount;
105
106        debug!(
107            "MerklePaymentMade event: winnerPoolHash={}, depth={}, totalAmount={}, timestamp={}",
108            hex::encode(winner_pool_hash),
109            event.depth,
110            total_amount,
111            event.merklePaymentTimestamp
112        );
113
114        Ok((winner_pool_hash, total_amount, gas_info))
115    }
116
117    /// Get calldata for payForMerkleTree.
118    ///
119    /// Public so external signers can generate calldata without a wallet.
120    pub fn pay_for_merkle_tree_calldata<I, T>(
121        &self,
122        depth: u8,
123        pool_commitments: I,
124        merkle_payment_timestamp: u64,
125    ) -> Result<(Calldata, Address), Error>
126    where
127        I: IntoIterator<Item = T>,
128        T: Into<IPaymentVault::PoolCommitment>,
129    {
130        let pool_commitments: Vec<IPaymentVault::PoolCommitment> =
131            pool_commitments.into_iter().map(Into::into).collect();
132        let calldata =
133            super::encode_merkle_payment(depth, pool_commitments, merkle_payment_timestamp).into();
134
135        Ok((calldata, *self.contract.address()))
136    }
137
138    /// Get completed merkle payment info for a winner pool hash.
139    ///
140    /// Calls `getCompletedMerklePayment` on the contract, which returns
141    /// `CompletedMerklePayment` containing depth, timestamp, and paid nodes
142    /// (each with rewards address, pool index, and amount).
143    pub async fn get_completed_merkle_payment(
144        &self,
145        winner_pool_hash: PoolHash,
146    ) -> Result<IPaymentVault::CompletedMerklePayment, Error> {
147        debug!(
148            "Getting completed merkle payment for pool hash: {}",
149            hex::encode(winner_pool_hash)
150        );
151
152        let info = self
153            .contract
154            .getCompletedMerklePayment(winner_pool_hash.into())
155            .call()
156            .await
157            .map_err(Error::Contract)?;
158
159        // Check if payment exists (depth == 0 means not found)
160        if info.depth == 0 {
161            return Err(Error::PaymentNotFound(hex::encode(winner_pool_hash)));
162        }
163
164        debug!(
165            "getCompletedMerklePayment returned: depth={}, timestamp={}, paid_nodes={}",
166            info.depth,
167            info.merklePaymentTimestamp,
168            info.paidNodeAddresses.len()
169        );
170
171        Ok(info)
172    }
173
174    // ── Private helpers ─────────────────────────────────────────────────
175
176    /// Get the MerklePaymentMade event from a transaction hash with retry logic.
177    ///
178    /// Retries up to 2 times with exponential backoff if the event is not found
179    /// immediately (handles cases where the transaction may not be fully indexed).
180    pub(crate) async fn get_merkle_payment_event(
181        &self,
182        tx_hash: TxHash,
183    ) -> Result<IPaymentVault::MerklePaymentMade, Error> {
184        const MAX_ATTEMPTS: u32 = 3;
185        const INITIAL_DELAY_MS: u64 = 500;
186        const MAX_DELAY_MS: u64 = 8000;
187
188        let backoff = Backoff::new(
189            MAX_ATTEMPTS,
190            Duration::from_millis(INITIAL_DELAY_MS),
191            Some(Duration::from_millis(MAX_DELAY_MS)),
192        );
193
194        let mut last_error = None;
195        let mut attempt = 1;
196
197        for duration_opt in backoff {
198            match self.try_get_merkle_payment_event(tx_hash).await {
199                Ok(event) => return Ok(event),
200                Err(e) => {
201                    last_error = Some(e);
202
203                    if let Some(duration) = duration_opt {
204                        debug!(
205                            "Failed to get MerklePaymentMade event (attempt {}/{}), retrying in {}ms",
206                            attempt,
207                            MAX_ATTEMPTS,
208                            duration.as_millis()
209                        );
210                        crate::runtime::sleep(duration).await;
211                    }
212                    attempt += 1;
213                }
214            }
215        }
216
217        Err(last_error.unwrap_or_else(|| {
218            Error::Rpc("Failed to get MerklePaymentMade event after retries".to_string())
219        }))
220    }
221
222    /// Try to get the MerklePaymentMade event from a transaction hash (single attempt)
223    async fn try_get_merkle_payment_event(
224        &self,
225        tx_hash: TxHash,
226    ) -> Result<IPaymentVault::MerklePaymentMade, Error> {
227        let tx = self
228            .contract
229            .provider()
230            .get_transaction_by_hash(tx_hash)
231            .await
232            .map_err(|e| Error::Rpc(format!("Failed to get transaction: {e}")))?
233            .ok_or_else(|| Error::Rpc("Transaction not found".to_string()))?;
234
235        let block_number = tx
236            .block_number()
237            .ok_or_else(|| Error::Rpc("Transaction has no block number".to_string()))?;
238
239        let events = self
240            .contract
241            .MerklePaymentMade_filter()
242            .from_block(block_number)
243            .to_block(block_number)
244            .query()
245            .await
246            .map_err(|e| Error::Rpc(format!("Failed to query MerklePaymentMade events: {e}")))?;
247
248        events
249            .into_iter()
250            .find(|(_, log)| log.transaction_hash == Some(tx_hash))
251            .map(|(event, _)| event)
252            .ok_or_else(|| {
253                Error::Rpc("MerklePaymentMade event not found in transaction".to_string())
254            })
255    }
256
257    /// Send transaction with retries and handle revert errors
258    async fn send_transaction_and_handle_errors(
259        &self,
260        calldata: Calldata,
261        to: Address,
262        transaction_config: &TransactionConfig,
263    ) -> Result<(TxHash, GasInfo), Error> {
264        let tx_result = crate::retry::send_transaction_with_retries(
265            self.contract.provider(),
266            calldata,
267            to,
268            "pay for merkle tree",
269            transaction_config,
270        )
271        .await;
272
273        match tx_result {
274            Ok((hash, gas_info)) => Ok((hash, gas_info)),
275            Err(TransactionError::TransactionReverted {
276                message,
277                revert_data,
278                nonce,
279            }) => {
280                let error = self.decode_revert_error(message, revert_data, nonce);
281                Err(error)
282            }
283            Err(other_err) => Err(Error::from(other_err)),
284        }
285    }
286
287    /// Decode revert data or return generic transaction error
288    fn decode_revert_error(
289        &self,
290        message: String,
291        revert_data: Option<alloy::primitives::Bytes>,
292        nonce: Option<u64>,
293    ) -> Error {
294        if let Some(revert_data_bytes) = &revert_data
295            && let Some(decoded_err) = Error::try_decode_revert(revert_data_bytes)
296        {
297            return decoded_err;
298        }
299
300        Error::Transaction(TransactionError::TransactionReverted {
301            message,
302            revert_data,
303            nonce,
304        })
305    }
306}