Skip to main content

evmlib/
lib.rs

1// Copyright 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9// Allow expect usage in this crate as it's used for compile-time constants
10#![allow(clippy::expect_used)]
11// Allow enum variant names that end with Error as they come from external derives
12#![allow(clippy::enum_variant_names)]
13
14use crate::common::{Address, Amount};
15use crate::contract::merkle_payment_vault::error::Error as MerklePaymentError;
16use crate::contract::merkle_payment_vault::handler::MerklePaymentVaultHandler;
17use crate::merkle_batch_payment::PoolCommitment;
18use crate::utils::{get_evm_network, http_provider};
19use alloy::primitives::address;
20use alloy::transports::http::reqwest;
21use serde::{Deserialize, Serialize};
22use serde_with::{DisplayFromStr, serde_as};
23use std::str::FromStr;
24use std::sync::LazyLock;
25
26#[macro_use]
27extern crate tracing;
28
29pub mod common;
30pub mod contract;
31pub mod cryptography;
32#[cfg(feature = "external-signer")]
33pub mod external_signer;
34pub mod merkle_batch_payment;
35pub mod quoting_metrics;
36mod retry;
37pub mod testnet;
38pub mod transaction_config;
39pub mod utils;
40pub mod wallet;
41
42// Re-export GasInfo for use by other crates
43pub use retry::GasInfo;
44
45/// Timeout for transactions
46const TX_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(24); // Should differ per chain
47
48static PUBLIC_ARBITRUM_ONE_HTTP_RPC_URL: LazyLock<reqwest::Url> = LazyLock::new(|| {
49    "https://arb1.arbitrum.io/rpc"
50        .parse()
51        .expect("Invalid RPC URL")
52});
53
54static PUBLIC_ARBITRUM_SEPOLIA_HTTP_RPC_URL: LazyLock<reqwest::Url> = LazyLock::new(|| {
55    "https://sepolia-rollup.arbitrum.io/rpc"
56        .parse()
57        .expect("Invalid RPC URL")
58});
59
60const ARBITRUM_ONE_PAYMENT_TOKEN_ADDRESS: Address =
61    address!("a78d8321B20c4Ef90eCd72f2588AA985A4BDb684");
62
63const ARBITRUM_SEPOLIA_TEST_PAYMENT_TOKEN_ADDRESS: Address =
64    address!("4bc1aCE0E66170375462cB4E6Af42Ad4D5EC689C");
65
66const ARBITRUM_ONE_DATA_PAYMENTS_ADDRESS: Address =
67    address!("B1b5219f8Aaa18037A2506626Dd0406a46f70BcC");
68
69const ARBITRUM_SEPOLIA_TEST_DATA_PAYMENTS_ADDRESS: Address =
70    address!("7f0842a78f7d4085d975ba91d630d680f91b1295");
71
72const ARBITRUM_ONE_MERKLE_PAYMENTS_ADDRESS: Address =
73    address!("0x8c20E9A6e5e2aA038Ed463460E412B669fE712Aa");
74
75const ARBITRUM_SEPOLIA_TEST_MERKLE_PAYMENTS_ADDRESS: Address =
76    address!("0x393F6825C248a29295A7f9Bfa03e475decb44dc0");
77
78#[serde_as]
79#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
80pub struct CustomNetwork {
81    #[serde_as(as = "DisplayFromStr")]
82    pub rpc_url_http: reqwest::Url,
83    pub payment_token_address: Address,
84    pub data_payments_address: Address,
85    pub merkle_payments_address: Option<Address>,
86}
87
88impl CustomNetwork {
89    pub fn new(
90        rpc_url: &str,
91        payment_token_addr: &str,
92        data_payments_addr: &str,
93        merkle_payments_addr: Option<&str>,
94    ) -> Self {
95        Self {
96            rpc_url_http: reqwest::Url::parse(rpc_url).expect("Invalid RPC URL"),
97            payment_token_address: Address::from_str(payment_token_addr)
98                .expect("Invalid payment token address"),
99            data_payments_address: Address::from_str(data_payments_addr)
100                .expect("Invalid chunk payments address"),
101            merkle_payments_address: merkle_payments_addr
102                .map(|addr| Address::from_str(addr).expect("Invalid merkle payments address")),
103        }
104    }
105}
106
107#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
108pub enum Network {
109    #[default]
110    ArbitrumOne,
111    ArbitrumSepoliaTest,
112    Custom(CustomNetwork),
113}
114
115impl std::fmt::Display for Network {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        match self {
118            Network::ArbitrumOne => write!(f, "evm-arbitrum-one"),
119            Network::ArbitrumSepoliaTest => write!(f, "evm-arbitrum-sepolia-test"),
120            Network::Custom(_) => write!(f, "evm-custom"),
121        }
122    }
123}
124
125impl std::str::FromStr for Network {
126    type Err = ();
127
128    fn from_str(s: &str) -> Result<Self, Self::Err> {
129        match s {
130            "evm-arbitrum-one" => Ok(Network::ArbitrumOne),
131            "evm-arbitrum-sepolia-test" => Ok(Network::ArbitrumSepoliaTest),
132            _ => Err(()),
133        }
134    }
135}
136
137impl Network {
138    pub fn new(local: bool) -> Result<Self, utils::Error> {
139        get_evm_network(local, None).inspect_err(|err| {
140            warn!("Failed to select EVM network from ENV: {err}");
141        })
142    }
143
144    pub fn new_custom(
145        rpc_url: &str,
146        payment_token_addr: &str,
147        chunk_payments_addr: &str,
148        merkle_payments_addr: Option<&str>,
149    ) -> Self {
150        Self::Custom(CustomNetwork::new(
151            rpc_url,
152            payment_token_addr,
153            chunk_payments_addr,
154            merkle_payments_addr,
155        ))
156    }
157
158    pub fn identifier(&self) -> &str {
159        match self {
160            Network::ArbitrumOne => "arbitrum-one",
161            Network::ArbitrumSepoliaTest => "arbitrum-sepolia-test",
162            Network::Custom(_) => "custom",
163        }
164    }
165
166    pub fn rpc_url(&self) -> &reqwest::Url {
167        match self {
168            Network::ArbitrumOne => &PUBLIC_ARBITRUM_ONE_HTTP_RPC_URL,
169            Network::ArbitrumSepoliaTest => &PUBLIC_ARBITRUM_SEPOLIA_HTTP_RPC_URL,
170            Network::Custom(custom) => &custom.rpc_url_http,
171        }
172    }
173
174    pub fn payment_token_address(&self) -> &Address {
175        match self {
176            Network::ArbitrumOne => &ARBITRUM_ONE_PAYMENT_TOKEN_ADDRESS,
177            Network::ArbitrumSepoliaTest => &ARBITRUM_SEPOLIA_TEST_PAYMENT_TOKEN_ADDRESS,
178            Network::Custom(custom) => &custom.payment_token_address,
179        }
180    }
181
182    pub fn data_payments_address(&self) -> &Address {
183        match self {
184            Network::ArbitrumOne => &ARBITRUM_ONE_DATA_PAYMENTS_ADDRESS,
185            Network::ArbitrumSepoliaTest => &ARBITRUM_SEPOLIA_TEST_DATA_PAYMENTS_ADDRESS,
186            Network::Custom(custom) => &custom.data_payments_address,
187        }
188    }
189
190    pub fn merkle_payments_address(&self) -> Option<&Address> {
191        match self {
192            Network::ArbitrumOne => Some(&ARBITRUM_ONE_MERKLE_PAYMENTS_ADDRESS),
193            Network::ArbitrumSepoliaTest => Some(&ARBITRUM_SEPOLIA_TEST_MERKLE_PAYMENTS_ADDRESS),
194            Network::Custom(custom) => custom.merkle_payments_address.as_ref(),
195        }
196    }
197
198    /// Estimate the cost of a Merkle tree batch using smart contract view function (0 gas).
199    ///
200    /// This calls the smart contract's view function which runs the exact same
201    /// pricing logic as the actual payment, ensuring accurate cost estimation.
202    /// No wallet is needed since view functions don't require signing.
203    ///
204    /// # Arguments
205    /// * `depth` - The Merkle tree depth
206    /// * `pool_commitments` - Vector of pool commitments with metrics (one per reward pool)
207    /// * `merkle_payment_timestamp` - Unix timestamp for the payment
208    ///
209    /// # Returns
210    /// * Estimated total cost in AttoTokens
211    pub async fn estimate_merkle_payment_cost(
212        &self,
213        depth: u8,
214        pool_commitments: &[PoolCommitment],
215        merkle_payment_timestamp: u64,
216    ) -> Result<Amount, MerklePaymentError> {
217        if pool_commitments.is_empty() {
218            return Ok(Amount::ZERO);
219        }
220
221        // Create provider (no wallet needed for view calls)
222        let provider = http_provider(self.rpc_url().clone());
223
224        // Get Merkle payment vault address
225        let merkle_vault_address = *self
226            .merkle_payments_address()
227            .ok_or(MerklePaymentError::MerklePaymentsAddressNotConfigured)?;
228
229        // Create handler and call the contract's view function
230        let handler = MerklePaymentVaultHandler::new(merkle_vault_address, provider);
231        let total_amount = handler
232            .estimate_merkle_tree_cost(depth, pool_commitments.to_vec(), merkle_payment_timestamp)
233            .await?;
234
235        Ok(total_amount)
236    }
237}