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::merkle_batch_payment::PoolCommitment;
16use crate::utils::get_evm_network;
17use alloy::primitives::address;
18use alloy::transports::http::reqwest;
19use serde::{Deserialize, Serialize};
20use serde_with::{DisplayFromStr, serde_as};
21use std::str::FromStr;
22use std::sync::LazyLock;
23
24#[macro_use]
25extern crate tracing;
26
27pub mod common;
28pub mod contract;
29pub mod cryptography;
30pub mod data_payments;
31#[cfg(feature = "external-signer")]
32pub mod external_signer;
33pub mod merkle_batch_payment;
34pub mod merkle_payments;
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// Re-export payment types for convenience (replaces ant-evm)
46pub use common::Address as RewardsAddress;
47pub use data_payments::{EncodedPeerId, PaymentQuote, ProofOfPayment};
48
49/// Timeout for transactions
50const TX_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(24); // Should differ per chain
51
52static PUBLIC_ARBITRUM_ONE_HTTP_RPC_URL: LazyLock<reqwest::Url> = LazyLock::new(|| {
53    "https://arb1.arbitrum.io/rpc"
54        .parse()
55        .expect("Invalid RPC URL")
56});
57
58static PUBLIC_ARBITRUM_SEPOLIA_HTTP_RPC_URL: LazyLock<reqwest::Url> = LazyLock::new(|| {
59    "https://sepolia-rollup.arbitrum.io/rpc"
60        .parse()
61        .expect("Invalid RPC URL")
62});
63
64const ARBITRUM_ONE_PAYMENT_TOKEN_ADDRESS: Address =
65    address!("a78d8321B20c4Ef90eCd72f2588AA985A4BDb684");
66
67const ARBITRUM_SEPOLIA_TEST_PAYMENT_TOKEN_ADDRESS: Address =
68    address!("4bc1aCE0E66170375462cB4E6Af42Ad4D5EC689C");
69
70/// Unified payment vault address (handles both single-node and merkle payments).
71const ARBITRUM_ONE_PAYMENT_VAULT_ADDRESS: Address =
72    address!("9A3EcAc693b699Fc0B2B6A50B5549e50c2320A26");
73
74/// Unified payment vault address on Arbitrum Sepolia (proxy contract).
75const ARBITRUM_SEPOLIA_TEST_PAYMENT_VAULT_ADDRESS: Address =
76    address!("d742E8CFEf27A9a884F3EFfA239Ee2F39c276522");
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    /// Unified payment vault handling both single-node and merkle payments.
85    pub payment_vault_address: Address,
86}
87
88impl CustomNetwork {
89    pub fn new(rpc_url: &str, payment_token_addr: &str, payment_vault_addr: &str) -> Self {
90        Self {
91            rpc_url_http: reqwest::Url::parse(rpc_url).expect("Invalid RPC URL"),
92            payment_token_address: Address::from_str(payment_token_addr)
93                .expect("Invalid payment token address"),
94            payment_vault_address: Address::from_str(payment_vault_addr)
95                .expect("Invalid payment vault address"),
96        }
97    }
98}
99
100#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
101pub enum Network {
102    #[default]
103    ArbitrumOne,
104    ArbitrumSepoliaTest,
105    Custom(CustomNetwork),
106}
107
108impl std::fmt::Display for Network {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        match self {
111            Network::ArbitrumOne => write!(f, "evm-arbitrum-one"),
112            Network::ArbitrumSepoliaTest => write!(f, "evm-arbitrum-sepolia-test"),
113            Network::Custom(_) => write!(f, "evm-custom"),
114        }
115    }
116}
117
118impl std::str::FromStr for Network {
119    type Err = ();
120
121    fn from_str(s: &str) -> Result<Self, Self::Err> {
122        match s {
123            "evm-arbitrum-one" => Ok(Network::ArbitrumOne),
124            "evm-arbitrum-sepolia-test" => Ok(Network::ArbitrumSepoliaTest),
125            _ => Err(()),
126        }
127    }
128}
129
130impl Network {
131    pub fn new(local: bool) -> Result<Self, utils::Error> {
132        get_evm_network(local, None).inspect_err(|err| {
133            warn!("Failed to select EVM network from ENV: {err}");
134        })
135    }
136
137    pub fn new_custom(rpc_url: &str, payment_token_addr: &str, payment_vault_addr: &str) -> Self {
138        Self::Custom(CustomNetwork::new(
139            rpc_url,
140            payment_token_addr,
141            payment_vault_addr,
142        ))
143    }
144
145    pub fn identifier(&self) -> &str {
146        match self {
147            Network::ArbitrumOne => "arbitrum-one",
148            Network::ArbitrumSepoliaTest => "arbitrum-sepolia-test",
149            Network::Custom(_) => "custom",
150        }
151    }
152
153    pub fn rpc_url(&self) -> &reqwest::Url {
154        match self {
155            Network::ArbitrumOne => &PUBLIC_ARBITRUM_ONE_HTTP_RPC_URL,
156            Network::ArbitrumSepoliaTest => &PUBLIC_ARBITRUM_SEPOLIA_HTTP_RPC_URL,
157            Network::Custom(custom) => &custom.rpc_url_http,
158        }
159    }
160
161    pub fn payment_token_address(&self) -> &Address {
162        match self {
163            Network::ArbitrumOne => &ARBITRUM_ONE_PAYMENT_TOKEN_ADDRESS,
164            Network::ArbitrumSepoliaTest => &ARBITRUM_SEPOLIA_TEST_PAYMENT_TOKEN_ADDRESS,
165            Network::Custom(custom) => &custom.payment_token_address,
166        }
167    }
168
169    /// Unified payment vault address (handles both single-node and merkle payments).
170    pub fn payment_vault_address(&self) -> &Address {
171        match self {
172            Network::ArbitrumOne => &ARBITRUM_ONE_PAYMENT_VAULT_ADDRESS,
173            Network::ArbitrumSepoliaTest => &ARBITRUM_SEPOLIA_TEST_PAYMENT_VAULT_ADDRESS,
174            Network::Custom(custom) => &custom.payment_vault_address,
175        }
176    }
177
178    /// Estimate the cost of a Merkle tree batch locally.
179    ///
180    /// Estimates the worst-case cost of a Merkle tree payment locally.
181    ///
182    /// The Solidity contract charges `median16(quotes) * (1 << depth)` for the
183    /// winning pool. The only unknown is which pool wins, so the worst case is
184    /// the pool with the highest `median(prices) * 2^depth`. No on-chain call
185    /// is made.
186    ///
187    /// # Arguments
188    /// * `depth` - The Merkle tree depth
189    /// * `pool_commitments` - Vector of pool commitments with prices (one per reward pool)
190    ///
191    /// # Returns
192    /// * Worst-case cost estimate in AttoTokens
193    pub fn estimate_merkle_payment_cost(
194        &self,
195        depth: u8,
196        pool_commitments: &[PoolCommitment],
197    ) -> Amount {
198        if pool_commitments.is_empty() {
199            return Amount::ZERO;
200        }
201
202        let multiplier = Amount::from(1u64 << depth);
203        pool_commitments
204            .iter()
205            .map(|pool| {
206                let mut prices: Vec<Amount> = pool.candidates.iter().map(|c| c.price).collect();
207                prices.sort_unstable(); // ascending
208                // Upper median (index 8 of 16) — matches Solidity's median16 (k = 8)
209                prices[prices.len() / 2] * multiplier
210            })
211            .max()
212            .unwrap_or(Amount::ZERO)
213    }
214}