1use std::{ops::Sub, str::FromStr};
27
28use alloy::primitives::{Address, U256, utils::parse_units};
29use clap::Parser;
30use hopr_bindings::hoprtoken::HoprToken;
31use tracing::info;
32
33use crate::{
34    environment_config::NetworkProviderArgs,
35    key_pair::{ArgEnvReader, IdentityFileArgs, PrivateKeyArgs},
36    methods::{get_native_and_token_balances, transfer_native_tokens, transfer_or_mint_tokens},
37    utils::{Cmd, HelperErrors},
38};
39
40#[derive(Parser, Default, Debug)]
42pub struct FaucetArgs {
43    #[clap(flatten)]
45    pub network_provider: NetworkProviderArgs,
46
47    #[clap(
49        help = "Comma-separated Ethereum addresses of nodes that will receive funds",
50        long,
51        short,
52        default_value = None
53    )]
54    address: Option<String>,
55
56    #[clap(flatten)]
58    local_identity: IdentityFileArgs,
59
60    #[clap(
62        help = "Hopr amount in ether, e.g. 10",
63        long,
64        short = 't',
65        value_parser = clap::value_parser!(f64),
66        default_value_t = 2000.0
67    )]
68    hopr_amount: f64,
69
70    #[clap(
72        help = "Native token amount in ether, e.g. 1",
73        long,
74        short = 'g',
75        value_parser = clap::value_parser!(f64),
76        default_value_t = 10.0
77    )]
78    native_amount: f64,
79
80    #[clap(flatten)]
83    pub private_key: PrivateKeyArgs,
84}
85
86impl FaucetArgs {
87    async fn execute_faucet(self) -> Result<(), HelperErrors> {
89        let FaucetArgs {
90            network_provider,
91            address,
92            local_identity,
93            hopr_amount,
94            native_amount,
95            private_key,
96        } = self;
97
98        let mut eth_addresses_all: Vec<Address> = Vec::new();
100        if let Some(addresses) = address {
101            eth_addresses_all.extend(addresses.split(',').map(|addr| {
102                Address::from_str(addr).unwrap_or_else(|e| {
103                    panic!(
104                        "{}",
105                        format!("Cannot parse address {addr:?} for eth_addresses_all, due to {e:?}")
106                    )
107                })
108            }));
109        }
110        eth_addresses_all.extend(local_identity.to_addresses()?.into_iter().map(Address::from));
112        info!("All the addresses: {:?}", eth_addresses_all);
113
114        let signer_private_key = private_key.read_default()?;
116
117        let rpc_provider = network_provider.get_provider_with_signer(&signer_private_key).await?;
119        let contract_addresses = network_provider.get_network_details_from_name()?;
120
121        let hopr_token = HoprToken::new(contract_addresses.addresses.token.into(), rpc_provider.clone());
122
123        let (native_balances, token_balances) =
126            get_native_and_token_balances(hopr_token.clone(), eth_addresses_all.clone())
127                .await
128                .map_err(HelperErrors::MulticallError)?;
129        let hopr_token_amount: U256 = parse_units(&hopr_amount.to_string(), "ether")
131            .map_err(|_| HelperErrors::ParseError("Failed to parse hopr_amount units".into()))?
132            .into();
133
134        let hopr_token_amounts: Vec<U256> = vec![hopr_token_amount; eth_addresses_all.len()]
135            .into_iter()
136            .enumerate()
137            .map(|(i, h)| {
138                if h.gt(&token_balances[i]) {
139                    let diff = h.sub(token_balances[i]);
140                    info!(
141                        "{:?} hopr-token to be transferred to {:?} to top up {:?}",
142                        diff, eth_addresses_all[i], &token_balances[i]
143                    );
144                    diff
145                } else {
146                    U256::ZERO
147                }
148            })
149            .collect();
150
151        let native_token_amount: U256 = parse_units(&native_amount.to_string(), "ether")
153            .map_err(|_| HelperErrors::ParseError("Failed to parse native_amount units".into()))?
154            .into();
155
156        let native_token_amounts: Vec<U256> = vec![native_token_amount; eth_addresses_all.len()]
157            .into_iter()
158            .enumerate()
159            .map(|(i, n)| {
160                if n.gt(&native_balances[i]) {
161                    let diff = n.sub(native_balances[i]);
162                    info!(
163                        "{:?} native-token to be transferred to {:?}, to top up {:?}",
164                        diff, eth_addresses_all[i], &native_balances[i]
165                    );
166                    diff
167                } else {
168                    U256::ZERO
169                }
170            })
171            .collect();
172
173        let total_transferred_hopr_token =
175            transfer_or_mint_tokens(hopr_token, eth_addresses_all.clone(), hopr_token_amounts).await?;
176        info!("total transferred hopr-token is {:?}", total_transferred_hopr_token);
177        let total_transferred_native_token =
179            transfer_native_tokens(rpc_provider, eth_addresses_all, native_token_amounts).await?;
180        info!("total transferred native-token is {:?}", total_transferred_native_token);
181        Ok(())
182    }
183}
184
185impl Cmd for FaucetArgs {
186    fn run(self) -> Result<(), HelperErrors> {
188        Ok(())
189    }
190
191    async fn async_run(self) -> Result<(), HelperErrors> {
192        self.execute_faucet().await
193    }
194}