From 0dce1d84cbb74684efc9a25b34ecca970fd154db Mon Sep 17 00:00:00 2001 From: Shashank Date: Fri, 23 May 2025 15:25:48 +0530 Subject: [PATCH 1/9] Restructure all modules --- src/app.rs | 191 +--------- src/constants.rs | 16 - src/faucet/calibnet/mod.rs | 10 + src/faucet/calibnet/views.rs | 34 ++ src/faucet/controller.rs | 247 ------------- src/faucet/mainnet/mod.rs | 10 + src/faucet/mainnet/views.rs | 29 ++ src/faucet/mod.rs | 266 ++++++++++++- src/faucet/model.rs | 18 - src/{ => faucet}/rate_limiter.rs | 3 +- src/faucet/{utils.rs => server.rs} | 94 +---- src/faucet/views.rs | 370 ------------------- src/faucet/views/alert.rs | 77 ++++ src/faucet/views/balance.rs | 55 +++ src/faucet/views/faucet.rs | 160 ++++++++ src/faucet/views/home.rs | 140 +++++++ src/faucet/views/icons.rs | 34 ++ src/faucet/views/layout.rs | 36 ++ src/faucet/views/mod.rs | 8 + src/faucet/views/nav.rs | 13 + src/faucet/views/transaction.rs | 81 ++++ src/icons/check_icon.rs | 15 - src/icons/lightning_icon.rs | 15 - src/icons/mod.rs | 5 - src/lib.rs | 43 ++- src/lotus_json/signed_message.rs | 47 --- src/message.rs | 63 ---- src/{ => utils}/address.rs | 0 src/{utils.rs => utils/error.rs} | 3 +- src/utils/format.rs | 76 ++++ src/{ => utils}/key.rs | 0 src/{ => utils}/lotus_json/address.rs | 0 src/{ => utils}/lotus_json/big_int.rs | 2 +- src/{ => utils}/lotus_json/cid.rs | 2 +- src/{ => utils}/lotus_json/message.rs | 16 +- src/{ => utils}/lotus_json/mod.rs | 7 +- src/{ => utils}/lotus_json/opt.rs | 0 src/{ => utils}/lotus_json/raw_bytes.rs | 0 src/{ => utils}/lotus_json/signature.rs | 4 +- src/{ => utils}/lotus_json/signature_type.rs | 2 +- src/utils/lotus_json/signed_message.rs | 85 +++++ src/{ => utils}/lotus_json/token_amount.rs | 2 +- src/{ => utils}/lotus_json/vec.rs | 0 src/{ => utils}/lotus_json/vec_u8.rs | 0 src/utils/message.rs | 18 + src/utils/mod.rs | 7 + src/{ => utils}/rpc_context.rs | 5 +- 47 files changed, 1203 insertions(+), 1106 deletions(-) delete mode 100644 src/constants.rs create mode 100644 src/faucet/calibnet/mod.rs create mode 100644 src/faucet/calibnet/views.rs delete mode 100644 src/faucet/controller.rs create mode 100644 src/faucet/mainnet/mod.rs create mode 100644 src/faucet/mainnet/views.rs delete mode 100644 src/faucet/model.rs rename src/{ => faucet}/rate_limiter.rs (94%) rename src/faucet/{utils.rs => server.rs} (59%) delete mode 100644 src/faucet/views.rs create mode 100644 src/faucet/views/alert.rs create mode 100644 src/faucet/views/balance.rs create mode 100644 src/faucet/views/faucet.rs create mode 100644 src/faucet/views/home.rs create mode 100644 src/faucet/views/icons.rs create mode 100644 src/faucet/views/layout.rs create mode 100644 src/faucet/views/mod.rs create mode 100644 src/faucet/views/nav.rs create mode 100644 src/faucet/views/transaction.rs delete mode 100644 src/icons/check_icon.rs delete mode 100644 src/icons/lightning_icon.rs delete mode 100644 src/icons/mod.rs delete mode 100644 src/lotus_json/signed_message.rs delete mode 100644 src/message.rs rename src/{ => utils}/address.rs (100%) rename src/{utils.rs => utils/error.rs} (99%) create mode 100644 src/utils/format.rs rename src/{ => utils}/key.rs (100%) rename src/{ => utils}/lotus_json/address.rs (100%) rename src/{ => utils}/lotus_json/big_int.rs (82%) rename src/{ => utils}/lotus_json/cid.rs (87%) rename src/{ => utils}/lotus_json/message.rs (83%) rename src/{ => utils}/lotus_json/mod.rs (99%) rename src/{ => utils}/lotus_json/opt.rs (100%) rename src/{ => utils}/lotus_json/raw_bytes.rs (100%) rename src/{ => utils}/lotus_json/signature.rs (89%) rename src/{ => utils}/lotus_json/signature_type.rs (93%) create mode 100644 src/utils/lotus_json/signed_message.rs rename src/{ => utils}/lotus_json/token_amount.rs (93%) rename src/{ => utils}/lotus_json/vec.rs (100%) rename src/{ => utils}/lotus_json/vec_u8.rs (100%) create mode 100644 src/utils/message.rs create mode 100644 src/utils/mod.rs rename src/{ => utils}/rpc_context.rs (96%) diff --git a/src/app.rs b/src/app.rs index af9363be..4cb140d9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,187 +1,12 @@ -use crate::icons::{CheckIcon, LightningIcon}; -use crate::rpc_context::{Provider, RpcContext}; -use fvm_shared::address::Network; +use crate::faucet::views::{faucet::Faucets, home::Explorer, layout::Footer}; +use crate::faucet::{calibnet::views::Faucet_Calibnet, mainnet::views::Faucet_Mainnet}; +use crate::utils::rpc_context::RpcContext; use leptos::prelude::*; -use leptos::{component, leptos_dom::helpers::event_target_value, view, IntoView}; +use leptos::{component, view, IntoView}; use leptos_meta::*; use leptos_router::components::*; use leptos_router::path; -#[allow(dead_code)] -pub fn shell(options: LeptosOptions) -> impl IntoView { - view! { - - - - Filecoin Forest Explorer Faucet - Get Free tFIL and FIL - - - - - - - - - - - } -} - -#[component] -pub fn Loader(loading: impl Fn() -> bool + 'static + Send) -> impl IntoView { - view! { } -} - -#[component] -pub fn BlockchainExplorer() -> impl IntoView { - let rpc_context = RpcContext::use_context(); - let network_name = LocalResource::new(move || { - let provider = rpc_context.get(); - async move { provider.network_name().await.ok() } - }); - - let network_version = LocalResource::new(move || { - let provider = rpc_context.get(); - async move { provider.network_version().await.ok() } - }); - - view! { -
-
-

- Filecoin Forest Explorer Faucet -

-

- The Filecoin Forest Explorer Faucet provides developers and users with free calibnet(tFil) and mainnet(FIL) to support their exploration, testing and development on the Filecoin network. -

-
- -
-
-

What does the faucet offer?

-
    -
  • - - Free calibnet tFIL for experimentation and development. -
  • -
  • - - - Real mainnet FIL for contributors engaging with the Filecoin ecosystem. - -
  • -
  • - - - A Quick and Easy way to request free tFIL and FIL - Just enter your wallet address. - -
  • -
-
- -
-

Why use this faucet?

-
    -
  • - - - Supports both calibnet and mainnet, unlike typical faucets. - -
  • -
  • - - - Enables testing of smart contracts, storage deals, and blockchain interactions without financial risk. - -
  • -
  • - - - Easy access to FIL for developers and users building on Filecoin. - -
  • -
  • - - - Need help? Visit the - - {" "} - Filecoin Slack - {" "}or - {" "} - documentation - . - -
  • -
-
-
- -
-
- -
- - - -
-
-
-

Network:

- Loading network name...

}> -

- {move || network_name.get().flatten()} - -

-
-
-
-

Version:

- Loading network version...

}> -

- {move || network_version.get().flatten()} - -

-
-
- -
- -
- } -} - -#[component] -fn Footer() -> impl IntoView { - view! { - - } -} - #[component] pub fn App() -> impl IntoView { provide_meta_context(); @@ -193,10 +18,10 @@ pub fn App() -> impl IntoView {
- - - - + + + +
diff --git a/src/constants.rs b/src/constants.rs deleted file mode 100644 index 3ee7aaa9..00000000 --- a/src/constants.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::sync::LazyLock; - -use fvm_shared::econ::TokenAmount; - -/// The rate limit imposed by the CloudFlare's rate limiter, and also reflected in the user -/// interface. -pub const CALIBNET_RATE_LIMIT_SECONDS: i64 = 60; -pub const MAINNET_RATE_LIMIT_SECONDS: i64 = 600; -/// The amount of mainnet FIL to be dripped to the user. This corresponds to 0.01 FIL. -pub static MAINNET_DRIP_AMOUNT: LazyLock = - LazyLock::new(|| TokenAmount::from_nano(10_000_000)); -/// The amount of calibnet tFIL to be dripped to the user. -pub static CALIBNET_DRIP_AMOUNT: LazyLock = - LazyLock::new(|| TokenAmount::from_whole(1)); -pub static FIL_MAINNET_UNIT: &str = "FIL"; -pub static FIL_CALIBNET_UNIT: &str = "tFIL"; diff --git a/src/faucet/calibnet/mod.rs b/src/faucet/calibnet/mod.rs new file mode 100644 index 00000000..68797b78 --- /dev/null +++ b/src/faucet/calibnet/mod.rs @@ -0,0 +1,10 @@ +pub mod views; + +use fvm_shared::econ::TokenAmount; +use std::sync::LazyLock; + +pub const CALIBNET_RATE_LIMIT_SECONDS: i64 = 60; +pub static FIL_CALIBNET_UNIT: &str = "tFIL"; +/// The amount of mainnet FIL to be dripped to the user. This corresponds to 1 tFIL. +pub static CALIBNET_DRIP_AMOUNT: LazyLock = + LazyLock::new(|| TokenAmount::from_whole(1)); diff --git a/src/faucet/calibnet/views.rs b/src/faucet/calibnet/views.rs new file mode 100644 index 00000000..8793732f --- /dev/null +++ b/src/faucet/calibnet/views.rs @@ -0,0 +1,34 @@ +use crate::faucet::calibnet::{ + CALIBNET_DRIP_AMOUNT, CALIBNET_RATE_LIMIT_SECONDS, FIL_CALIBNET_UNIT, +}; +use crate::faucet::views::faucet::Faucet; +use crate::utils::format::format_balance; +use crate::utils::rpc_context::{Provider, RpcContext}; +use fvm_shared::address::Network; +use leptos::prelude::*; +use leptos::{component, view, IntoView}; +use leptos_meta::{Meta, Title}; + +#[component] +pub fn Faucet_Calibnet() -> impl IntoView { + let rpc_context = RpcContext::use_context(); + // Set rpc context to calibnet url + rpc_context.set(Provider::get_network_url(Network::Testnet)); + + view! { + + <Meta + name="description" + content="Filecoin Calibration Network Faucet dispensing tokens for testing purposes." + /> + <div> + <h1 class="text-4xl font-bold mb-6 text-center">Filecoin Calibnet Faucet</h1> + <Faucet target_network=Network::Testnet /> + </div> + <div class="text-center mt-4"> + "This faucet distributes " {format_balance(&CALIBNET_DRIP_AMOUNT, FIL_CALIBNET_UNIT)} + " per request. It is rate-limited to 1 request per " {CALIBNET_RATE_LIMIT_SECONDS} + " seconds. Farming is discouraged and will result in more stringent rate limiting in the future and/or permanent bans." + </div> + } +} diff --git a/src/faucet/controller.rs b/src/faucet/controller.rs deleted file mode 100644 index fdd83317..00000000 --- a/src/faucet/controller.rs +++ /dev/null @@ -1,247 +0,0 @@ -use super::{model::FaucetModel, utils::sign_with_secret_key}; -use cid::Cid; -use fvm_shared::{address::Network, econ::TokenAmount}; -use leptos::prelude::*; -use leptos::task::spawn_local; -use uuid::Uuid; - -use crate::{ - address::parse_address, - constants::{CALIBNET_RATE_LIMIT_SECONDS, MAINNET_RATE_LIMIT_SECONDS}, - lotus_json::LotusJson, - message::message_transfer, - rpc_context::Provider, - utils::catch_all, -}; - -use super::utils::faucet_address; - -#[derive(Clone)] -pub(super) struct FaucetController { - faucet: FaucetModel, -} - -impl FaucetController { - pub fn new(network: Network) -> Self { - let is_mainnet = network == Network::Mainnet; - let balance_trigger = Trigger::new(); - let sender_address = RwSignal::new(String::new()); - let target_address = RwSignal::new(String::new()); - let target_balance = LocalResource::new(move || { - let target_address = target_address.get(); - balance_trigger.track(); - async move { - if let Ok(address) = parse_address(&target_address, network) { - Provider::from_network(network) - .wallet_balance(address) - .await - .ok() - .unwrap_or(TokenAmount::from_atto(0)) - } else { - TokenAmount::from_atto(0) - } - } - }); - let faucet_address = LocalResource::new(move || async move { - faucet_address(is_mainnet) - .await - .map(|LotusJson(addr)| addr) - .ok() - }); - let faucet_balance = LocalResource::new(move || { - balance_trigger.track(); - async move { - if let Some(addr) = faucet_address.await { - sender_address.set(addr.to_string()); - Provider::from_network(network) - .wallet_balance(addr) - .await - .ok() - .unwrap_or(TokenAmount::from_atto(0)) - } else { - TokenAmount::from_atto(0) - } - } - }); - let faucet = FaucetModel { - network, - send_disabled: RwSignal::new(false), - send_limited: RwSignal::new(0), - sent_messages: RwSignal::new(Vec::new()), - error_messages: RwSignal::new(Vec::new()), - balance_trigger, - target_balance, - faucet_balance, - sender_address, - target_address, - }; - Self { faucet } - } - - #[allow(dead_code)] - pub fn refetch_balances(&self) { - use leptos::prelude::GetUntracked; - - log::info!("Checking for new transactions"); - self.faucet.balance_trigger.notify(); - let pending = self - .faucet - .sent_messages - .get_untracked() - .into_iter() - .filter_map(|(cid, sent)| if !sent { Some(cid) } else { None }) - .collect::<Vec<_>>(); - - let network = self.faucet.network; - let messages = self.faucet.sent_messages; - spawn_local(catch_all(self.faucet.error_messages, async move { - for cid in pending { - if let Some(lookup) = Provider::from_network(network) - .state_search_msg(cid) - .await? - { - messages.update(|messages| { - for (cid, sent) in messages { - if cid == &lookup.message { - *sent = true; - } - } - }); - } - } - Ok(()) - })); - } - pub fn get_target_balance(&self) -> TokenAmount { - self.faucet.target_balance.get().unwrap_or_default() - } - - pub fn get_sender_address(&self) -> String { - self.faucet.sender_address.get() - } - - pub fn get_target_address(&self) -> String { - self.faucet.target_address.get() - } - - pub fn get_fil_unit(&self) -> String { - match self.faucet.network { - Network::Mainnet => crate::constants::FIL_MAINNET_UNIT, - _ => crate::constants::FIL_CALIBNET_UNIT, - } - .to_string() - } - - pub fn set_target_address(&self, address: String) { - self.faucet.target_address.set(address); - } - - pub fn get_faucet_balance(&self) -> TokenAmount { - self.faucet.faucet_balance.get().unwrap_or_default() - } - - pub fn get_error_messages(&self) -> Vec<(Uuid, String)> { - self.faucet.error_messages.get().clone() - } - - pub fn add_error_message(&self, message: String) { - self.faucet.error_messages.update(|messages| { - messages.push((Uuid::new_v4(), message)); - }); - } - - pub fn remove_error_message(&self, id: Uuid) { - self.faucet.error_messages.update(|messages| { - messages.retain(|(x, _)| *x != id); - }); - } - - pub fn get_sent_messages(&self) -> Vec<(Cid, bool)> { - self.faucet.sent_messages.get().clone() - } - - pub fn is_send_disabled(&self) -> bool { - self.faucet.send_disabled.get() - } - - pub fn get_send_rate_limit_remaining(&self) -> i32 { - self.faucet.send_limited.get() - } - - #[allow(dead_code)] - pub fn set_send_rate_limit_remaining(&self, remaining: i32) { - self.faucet.send_limited.set(remaining); - } - - fn get_drip_amount(&self) -> TokenAmount { - if self.faucet.network == Network::Mainnet { - crate::constants::MAINNET_DRIP_AMOUNT.clone() - } else { - crate::constants::CALIBNET_DRIP_AMOUNT.clone() - } - } - - pub fn is_low_balance(&self) -> bool { - let target_balance = self.get_faucet_balance(); - let drip_amount = self.get_drip_amount(); - target_balance < drip_amount - } - - pub fn drip(&self) { - let is_mainnet = self.faucet.network == Network::Mainnet; - let faucet = self.faucet.clone(); - match parse_address(&self.faucet.target_address.get(), self.faucet.network) { - Ok(addr) => { - spawn_local(async move { - catch_all(faucet.error_messages, async move { - let rpc = Provider::from_network(faucet.network); - let LotusJson(from) = faucet_address(is_mainnet) - .await - .map_err(|e| anyhow::anyhow!("Error getting faucet address: {}", e))?; - faucet.send_disabled.set(true); - let nonce = rpc.mpool_get_nonce(from).await?; - let mut msg = message_transfer( - from, - addr, - if is_mainnet { - crate::constants::MAINNET_DRIP_AMOUNT.clone() - } else { - crate::constants::CALIBNET_DRIP_AMOUNT.clone() - }, - ); - msg.sequence = nonce; - let msg = rpc.estimate_gas(msg).await?; - match sign_with_secret_key(LotusJson(msg.clone()), is_mainnet).await { - Ok(LotusJson(smsg)) => { - let cid = rpc.mpool_push(smsg).await?; - faucet.sent_messages.update(|messages| { - messages.push((cid, false)); - }); - log::info!("Sent message: {:?}", cid); - } - Err(e) => { - log::error!("Failed to sign message: {}", e); - let rate_limit_seconds = if is_mainnet { - MAINNET_RATE_LIMIT_SECONDS - } else { - CALIBNET_RATE_LIMIT_SECONDS - }; - faucet.send_limited.set(rate_limit_seconds as i32); - } - } - Ok(()) - }) - .await; - faucet.send_disabled.set(false); - }); - } - Err(e) => { - self.add_error_message(format!( - "Invalid address: {}", - &self.faucet.target_address.get() - )); - log::error!("Error parsing address: {}", e); - } - } - } -} diff --git a/src/faucet/mainnet/mod.rs b/src/faucet/mainnet/mod.rs new file mode 100644 index 00000000..17ab989c --- /dev/null +++ b/src/faucet/mainnet/mod.rs @@ -0,0 +1,10 @@ +pub mod views; + +use fvm_shared::econ::TokenAmount; +use std::sync::LazyLock; + +pub const MAINNET_RATE_LIMIT_SECONDS: i64 = 600; +pub static FIL_MAINNET_UNIT: &str = "FIL"; +/// The amount of mainnet FIL to be dripped to the user. This corresponds to 0.01 FIL. +pub static MAINNET_DRIP_AMOUNT: LazyLock<TokenAmount> = + LazyLock::new(|| TokenAmount::from_nano(10_000_000)); diff --git a/src/faucet/mainnet/views.rs b/src/faucet/mainnet/views.rs new file mode 100644 index 00000000..de3acae5 --- /dev/null +++ b/src/faucet/mainnet/views.rs @@ -0,0 +1,29 @@ +use crate::faucet::mainnet::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS}; +use crate::faucet::views::faucet::Faucet; +use crate::utils::format::format_balance; +use crate::utils::rpc_context::{Provider, RpcContext}; +use fvm_shared::address::Network; +use leptos::prelude::*; +use leptos::{component, view, IntoView}; +use leptos_meta::{Meta, Title}; + +#[component] +pub fn Faucet_Mainnet() -> impl IntoView { + let rpc_context = RpcContext::use_context(); + // Set rpc context to mainnet url + rpc_context.set(Provider::get_network_url(Network::Mainnet)); + + view! { + <Title text="Filecoin Faucet - Mainnet" /> + <Meta name="description" content="Filecoin Mainnet Faucet dispensing tokens for testing purposes." /> + <div> + <h1 class="text-4xl font-bold mb-6 text-center">Filecoin Mainnet Faucet</h1> + <Faucet target_network=Network::Mainnet /> + <div class="text-center mt-4"> + "This faucet distributes " {format_balance(&MAINNET_DRIP_AMOUNT, FIL_MAINNET_UNIT)} + " per request. It is rate-limited to 1 request per " {MAINNET_RATE_LIMIT_SECONDS} + " seconds. Farming is discouraged and will result in more stringent rate limiting in the future and/or permanent bans or service termination. Faucet funds are limited and may run out. They are replenished periodically." + </div> + </div> + } +} diff --git a/src/faucet/mod.rs b/src/faucet/mod.rs index d5de990f..1cab0ab9 100644 --- a/src/faucet/mod.rs +++ b/src/faucet/mod.rs @@ -1,4 +1,264 @@ -mod controller; -mod model; -pub mod utils; +#[cfg(feature = "ssr")] +mod rate_limiter; + +pub mod calibnet; +pub mod mainnet; +pub mod server; pub mod views; + +use calibnet::{CALIBNET_DRIP_AMOUNT, CALIBNET_RATE_LIMIT_SECONDS, FIL_CALIBNET_UNIT}; +use mainnet::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS}; + +use crate::utils::lotus_json::LotusJson; +use crate::utils::rpc_context::Provider; +use crate::utils::{address::parse_address, error::catch_all, message::message_transfer}; +use cid::Cid; +use fvm_shared::{address::Network, econ::TokenAmount}; +use leptos::prelude::*; +use leptos::task::spawn_local; +use server::{faucet_address, sign_with_secret_key}; +use uuid::Uuid; + +#[derive(Clone)] +pub(super) struct FaucetModel { + pub network: Network, + pub send_disabled: RwSignal<bool>, + pub send_limited: RwSignal<i32>, + pub sent_messages: RwSignal<Vec<(Cid, bool)>>, + pub error_messages: RwSignal<Vec<(Uuid, String)>>, + pub balance_trigger: Trigger, + pub faucet_balance: LocalResource<TokenAmount>, + pub target_balance: LocalResource<TokenAmount>, + pub sender_address: RwSignal<String>, + pub target_address: RwSignal<String>, +} + +#[derive(Clone)] +pub(super) struct FaucetController { + faucet: FaucetModel, +} + +impl FaucetController { + pub fn new(network: Network) -> Self { + let is_mainnet = network == Network::Mainnet; + let balance_trigger = Trigger::new(); + let sender_address = RwSignal::new(String::new()); + let target_address = RwSignal::new(String::new()); + let target_balance = LocalResource::new(move || { + let target_address = target_address.get(); + balance_trigger.track(); + async move { + if let Ok(address) = parse_address(&target_address, network) { + Provider::from_network(network) + .wallet_balance(address) + .await + .ok() + .unwrap_or(TokenAmount::from_atto(0)) + } else { + TokenAmount::from_atto(0) + } + } + }); + let faucet_address = LocalResource::new(move || async move { + faucet_address(is_mainnet) + .await + .map(|LotusJson(addr)| addr) + .ok() + }); + let faucet_balance = LocalResource::new(move || { + balance_trigger.track(); + async move { + if let Some(addr) = faucet_address.await { + sender_address.set(addr.to_string()); + Provider::from_network(network) + .wallet_balance(addr) + .await + .ok() + .unwrap_or(TokenAmount::from_atto(0)) + } else { + TokenAmount::from_atto(0) + } + } + }); + let faucet = FaucetModel { + network, + send_disabled: RwSignal::new(false), + send_limited: RwSignal::new(0), + sent_messages: RwSignal::new(Vec::new()), + error_messages: RwSignal::new(Vec::new()), + balance_trigger, + target_balance, + faucet_balance, + sender_address, + target_address, + }; + Self { faucet } + } + + #[allow(dead_code)] + pub fn refetch_balances(&self) { + use leptos::prelude::GetUntracked; + + log::info!("Checking for new transactions"); + self.faucet.balance_trigger.notify(); + let pending = self + .faucet + .sent_messages + .get_untracked() + .into_iter() + .filter_map(|(cid, sent)| if !sent { Some(cid) } else { None }) + .collect::<Vec<_>>(); + + let network = self.faucet.network; + let messages = self.faucet.sent_messages; + spawn_local(catch_all(self.faucet.error_messages, async move { + for cid in pending { + if let Some(lookup) = Provider::from_network(network) + .state_search_msg(cid) + .await? + { + messages.update(|messages| { + for (cid, sent) in messages { + if cid == &lookup.message { + *sent = true; + } + } + }); + } + } + Ok(()) + })); + } + pub fn get_target_balance(&self) -> TokenAmount { + self.faucet.target_balance.get().unwrap_or_default() + } + + pub fn get_sender_address(&self) -> String { + self.faucet.sender_address.get() + } + + pub fn get_target_address(&self) -> String { + self.faucet.target_address.get() + } + + pub fn get_fil_unit(&self) -> String { + match self.faucet.network { + Network::Mainnet => FIL_MAINNET_UNIT, + _ => FIL_CALIBNET_UNIT, + } + .to_string() + } + + pub fn set_target_address(&self, address: String) { + self.faucet.target_address.set(address); + } + + pub fn get_faucet_balance(&self) -> TokenAmount { + self.faucet.faucet_balance.get().unwrap_or_default() + } + + pub fn get_error_messages(&self) -> Vec<(Uuid, String)> { + self.faucet.error_messages.get().clone() + } + + pub fn add_error_message(&self, message: String) { + self.faucet.error_messages.update(|messages| { + messages.push((Uuid::new_v4(), message)); + }); + } + + pub fn remove_error_message(&self, id: Uuid) { + self.faucet.error_messages.update(|messages| { + messages.retain(|(x, _)| *x != id); + }); + } + + pub fn get_sent_messages(&self) -> Vec<(Cid, bool)> { + self.faucet.sent_messages.get().clone() + } + + pub fn is_send_disabled(&self) -> bool { + self.faucet.send_disabled.get() + } + + pub fn get_send_rate_limit_remaining(&self) -> i32 { + self.faucet.send_limited.get() + } + + #[allow(dead_code)] + pub fn set_send_rate_limit_remaining(&self, remaining: i32) { + self.faucet.send_limited.set(remaining); + } + + fn get_drip_amount(&self) -> TokenAmount { + if self.faucet.network == Network::Mainnet { + MAINNET_DRIP_AMOUNT.clone() + } else { + CALIBNET_DRIP_AMOUNT.clone() + } + } + + pub fn is_low_balance(&self) -> bool { + let target_balance = self.get_faucet_balance(); + let drip_amount = self.get_drip_amount(); + target_balance < drip_amount + } + + pub fn drip(&self) { + let is_mainnet = self.faucet.network == Network::Mainnet; + let faucet = self.faucet.clone(); + match parse_address(&self.faucet.target_address.get(), self.faucet.network) { + Ok(addr) => { + spawn_local(async move { + catch_all(faucet.error_messages, async move { + let rpc = Provider::from_network(faucet.network); + let LotusJson(from) = faucet_address(is_mainnet) + .await + .map_err(|e| anyhow::anyhow!("Error getting faucet address: {}", e))?; + faucet.send_disabled.set(true); + let nonce = rpc.mpool_get_nonce(from).await?; + let mut msg = message_transfer( + from, + addr, + if is_mainnet { + MAINNET_DRIP_AMOUNT.clone() + } else { + CALIBNET_DRIP_AMOUNT.clone() + }, + ); + msg.sequence = nonce; + let msg = rpc.estimate_gas(msg).await?; + match sign_with_secret_key(LotusJson(msg.clone()), is_mainnet).await { + Ok(LotusJson(smsg)) => { + let cid = rpc.mpool_push(smsg).await?; + faucet.sent_messages.update(|messages| { + messages.push((cid, false)); + }); + log::info!("Sent message: {:?}", cid); + } + Err(e) => { + log::error!("Failed to sign message: {}", e); + let rate_limit_seconds = if is_mainnet { + MAINNET_RATE_LIMIT_SECONDS + } else { + CALIBNET_RATE_LIMIT_SECONDS + }; + faucet.send_limited.set(rate_limit_seconds as i32); + } + } + Ok(()) + }) + .await; + faucet.send_disabled.set(false); + }); + } + Err(e) => { + self.add_error_message(format!( + "Invalid address: {}", + &self.faucet.target_address.get() + )); + log::error!("Error parsing address: {}", e); + } + } + } +} diff --git a/src/faucet/model.rs b/src/faucet/model.rs deleted file mode 100644 index 0e58058b..00000000 --- a/src/faucet/model.rs +++ /dev/null @@ -1,18 +0,0 @@ -use cid::Cid; -use fvm_shared::{address::Network, econ::TokenAmount}; -use leptos::prelude::{LocalResource, RwSignal, Trigger}; -use uuid::Uuid; - -#[derive(Clone)] -pub(super) struct FaucetModel { - pub network: Network, - pub send_disabled: RwSignal<bool>, - pub send_limited: RwSignal<i32>, - pub sent_messages: RwSignal<Vec<(Cid, bool)>>, - pub error_messages: RwSignal<Vec<(Uuid, String)>>, - pub balance_trigger: Trigger, - pub faucet_balance: LocalResource<TokenAmount>, - pub target_balance: LocalResource<TokenAmount>, - pub sender_address: RwSignal<String>, - pub target_address: RwSignal<String>, -} diff --git a/src/rate_limiter.rs b/src/faucet/rate_limiter.rs similarity index 94% rename from src/rate_limiter.rs rename to src/faucet/rate_limiter.rs index 6ecc7b50..aa9355e7 100644 --- a/src/rate_limiter.rs +++ b/src/faucet/rate_limiter.rs @@ -1,4 +1,5 @@ -use crate::constants::{CALIBNET_RATE_LIMIT_SECONDS, MAINNET_RATE_LIMIT_SECONDS}; +use crate::faucet::calibnet::CALIBNET_RATE_LIMIT_SECONDS; +use crate::faucet::mainnet::MAINNET_RATE_LIMIT_SECONDS; use chrono::{DateTime, Duration, Utc}; use worker::*; diff --git a/src/faucet/utils.rs b/src/faucet/server.rs similarity index 59% rename from src/faucet/utils.rs rename to src/faucet/server.rs index ac110304..845c7b00 100644 --- a/src/faucet/utils.rs +++ b/src/faucet/server.rs @@ -1,12 +1,11 @@ #[cfg(feature = "ssr")] -use crate::key::{sign, Key}; -use crate::{lotus_json::LotusJson, message::SignedMessage}; -use anyhow::{anyhow, Result}; +use crate::utils::key::{sign, Key}; +use crate::utils::lotus_json::{signed_message::SignedMessage, LotusJson}; +use anyhow::Result; #[cfg(feature = "ssr")] use fvm_shared::address::Network; -use fvm_shared::{address::Address, econ::TokenAmount, message::Message}; +use fvm_shared::{address::Address, message::Message}; use leptos::{prelude::ServerFnError, server}; -use url::Url; #[server] pub async fn faucet_address(is_mainnet: bool) -> Result<LotusJson<Address>, ServerFnError> { @@ -24,14 +23,14 @@ pub async fn sign_with_secret_key( msg: LotusJson<Message>, is_mainnet: bool, ) -> Result<LotusJson<SignedMessage>, ServerFnError> { - use crate::message::message_cid; + use crate::utils::lotus_json::signed_message::message_cid; use leptos::server_fn::error; use send_wrapper::SendWrapper; let LotusJson(msg) = msg; let cid = message_cid(&msg); let amount_limit = match is_mainnet { - true => crate::constants::MAINNET_DRIP_AMOUNT.clone(), - false => crate::constants::CALIBNET_DRIP_AMOUNT.clone(), + true => crate::faucet::mainnet::MAINNET_DRIP_AMOUNT.clone(), + false => crate::faucet::calibnet::CALIBNET_DRIP_AMOUNT.clone(), }; if msg.value > amount_limit { return Err(ServerFnError::ServerError( @@ -51,9 +50,9 @@ pub async fn sign_with_secret_key( let network = if is_mainnet { "mainnet" } else { "calibnet" }; let may_sign = rate_limiter_disabled || query_rate_limiter(network).await?; let rate_limit_seconds = if is_mainnet { - crate::constants::MAINNET_RATE_LIMIT_SECONDS + crate::faucet::mainnet::MAINNET_RATE_LIMIT_SECONDS } else { - crate::constants::CALIBNET_RATE_LIMIT_SECONDS + crate::faucet::calibnet::CALIBNET_RATE_LIMIT_SECONDS }; if !may_sign { return Err(ServerFnError::ServerError(format!( @@ -85,7 +84,7 @@ pub async fn sign_with_secret_key( #[cfg(feature = "ssr")] pub async fn secret_key(network: Network) -> Result<Key, ServerFnError> { - use crate::key::KeyInfo; + use crate::utils::key::KeyInfo; use axum::Extension; use leptos::server_fn::error; use leptos_axum::extract; @@ -125,76 +124,3 @@ pub async fn query_rate_limiter(network: &str) -> Result<bool, ServerFnError> { .json::<bool>() .await?) } - -/// Formats FIL balance to a human-readable string with two decimal places and a unit. -pub fn format_balance(balance: &TokenAmount, unit: &str) -> String { - format!( - "{:.2} {unit}", - balance.to_string().parse::<f32>().unwrap_or_default(), - ) -} - -/// Types of search paths in Filecoin explorer. -#[derive(Copy, Clone)] -pub enum SearchPath { - Transaction, - Address, -} - -impl SearchPath { - pub fn as_str(&self) -> &'static str { - match self { - SearchPath::Transaction => "txs/", - SearchPath::Address => "address/", - } - } -} - -/// Constructs a URL combining base URL, search path, and an identifier. -pub fn format_url(base_url: &Url, path: SearchPath, identifier: &str) -> Result<Url> { - base_url - .join(path.as_str())? - .join(identifier) - .map_err(|e| anyhow!("Failed to join URL: {}", e)) -} - -#[cfg(test)] -mod tests { - use super::*; - use fvm_shared::econ::TokenAmount; - - #[test] - fn test_format_balance() { - let cases = [ - (TokenAmount::from_whole(1), "1.00 FIL"), - (TokenAmount::from_whole(0), "0.00 FIL"), - (TokenAmount::from_nano(10e6 as i64), "0.01 FIL"), - (TokenAmount::from_nano(999_999_999), "1.00 FIL"), - ]; - for (balance, expected) in cases.iter() { - assert_eq!(format_balance(balance, "FIL"), *expected); - } - } - - #[test] - fn test_format_url() { - let base = Url::parse("https://test.com/").unwrap(); - let cases = [ - ( - SearchPath::Transaction, - "0xdef456", - "https://test.com/txs/0xdef456", - ), - ( - SearchPath::Address, - "0xabc123", - "https://test.com/address/0xabc123", - ), - ]; - - for (path, query, expected) in cases.iter() { - let result = format_url(&base, *path, query).unwrap(); - assert_eq!(result.as_str(), *expected); - } - } -} diff --git a/src/faucet/views.rs b/src/faucet/views.rs deleted file mode 100644 index 31bbf063..00000000 --- a/src/faucet/views.rs +++ /dev/null @@ -1,370 +0,0 @@ -use std::collections::HashSet; -use std::time::Duration; - -use fvm_shared::address::Network; -use leptos::prelude::*; -use leptos::task::spawn_local; -use leptos::{component, leptos_dom::helpers::event_target_value, view, IntoView}; -use leptos_meta::{Meta, Title}; -#[cfg(feature = "hydrate")] -use leptos_use::*; -use url::Url; - -use crate::faucet::controller::FaucetController; -use crate::faucet::utils::SearchPath; -use crate::faucet::utils::{format_balance, format_url}; -use crate::rpc_context::{Provider, RpcContext}; - -const MESSAGE_FADE_AFTER: Duration = Duration::new(3, 0); -const MESSAGE_REMOVAL_AFTER: Duration = Duration::new(3, 500_000_000); - -#[component] -fn FaucetBalance(faucet: RwSignal<FaucetController>) -> impl IntoView { - view! { - <div> - <h3 class="text-lg font-semibold">Faucet Balance:</h3> - <Transition fallback=move || { - view! { <p>Loading faucet balance...</p> } - }> - {move || { - if faucet.get().is_low_balance() { - let topup_req_url = option_env!("FAUCET_TOPUP_REQ_URL"); - view! { - <a - class="bg-orange-500 hover:bg-orange-600 text-white font-bold text-sm py-1 px-2 rounded" - target="_blank" - rel="noopener noreferrer" - href=topup_req_url - > - "Request Faucet Top-up" - </a> - } - .into_any() - } else { - view! { - <p class="text-xl"> - {format_balance(&faucet.get().get_faucet_balance(), &faucet.get().get_fil_unit())} - </p> - } - .into_any() - } - }} - </Transition> - </div> - } -} - -#[component] -fn TargetBalance(faucet: RwSignal<FaucetController>) -> impl IntoView { - view! { - <div> - <h3 class="text-lg font-semibold">Target Balance:</h3> - <Transition fallback=move || view! { <p>Loading target balance...</p> }> - <p class="text-xl"> - {move || format_balance(&faucet.get().get_target_balance(), &faucet.get().get_fil_unit())} - </p> - </Transition> - </div> - } -} - -#[component] -pub fn Faucet(target_network: Network) -> impl IntoView { - let faucet = RwSignal::new(FaucetController::new(target_network)); - - #[cfg(feature = "hydrate")] - let _ = use_interval_fn( - move || { - let duration = faucet.get().get_send_rate_limit_remaining(); - if duration > 0 { - faucet.get().set_send_rate_limit_remaining(duration - 1); - } - }, - 1000, - ); - - #[cfg(feature = "hydrate")] - let _ = use_interval_fn( - move || { - faucet.get().refetch_balances(); - }, - 5000, - ); - - let (fading_messages, set_fading_messages) = signal(HashSet::new()); - let faucet_tx_base_url = match target_network { - Network::Mainnet => { - RwSignal::new(option_env!("FAUCET_TX_URL_MAINNET").and_then(|url| Url::parse(url).ok())) - } - Network::Testnet => RwSignal::new( - option_env!("FAUCET_TX_URL_CALIBNET").and_then(|url| Url::parse(url).ok()), - ), - }; - view! { - {move || { - let errors = faucet.get().get_error_messages(); - if !errors.is_empty() { - view! { - <div class="fixed top-4 left-1/2 transform -translate-x-1/2 z-50"> - {errors - .into_iter() - .map(|(id, error)| { - spawn_local(async move { - set_timeout( - move || { - set_fading_messages - .update(|fading| { - fading.insert(id); - }); - }, - MESSAGE_FADE_AFTER, - ); - set_timeout( - move || { - set_fading_messages - .update(|fading| { - fading.remove(&id); - }); - faucet.get().remove_error_message(id); - }, - MESSAGE_REMOVAL_AFTER, - ); - }); - // Start fading message after 3 seconds - - // Remove message after 3.5 seconds - - view! { - <div - class=move || { - if fading_messages.get().contains(&id) { - "opacity-0 transition-opacity bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-2 w-96" - } else { - "bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-2 w-96" - } - } - role="alert" - > - <span class="block sm:inline">{error}</span> - <span class="absolute top-0 bottom-0 right-0 px-4 py-3"> - <svg - class="fill-current h-6 w-6 text-red-500" - role="button" - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 20 20" - on:click=move |_| { - faucet.get().remove_error_message(id); - } - > - <title>Close - - -
- - } - }) - .collect::>()} - - } - .into_any() - } else { - ().into_any() - } - }} -
-
- - {move || { - if faucet.get().is_send_disabled() { - view! { - - } - .into_any() - } else if faucet.get().get_send_rate_limit_remaining() > 0 { - let duration = faucet.get().get_send_rate_limit_remaining(); - view! { - - } - .into_any() - } else if faucet.get().is_low_balance() { - view! { - - } - .into_any() - } else { - view! { - - } - .into_any() - } - }} -
-
- - -
-
- {move || { - let messages = faucet.get().get_sent_messages(); - if !messages.is_empty() { - view! { -
-

Transactions:

-
    - {messages - .into_iter() - .map(|(msg, sent)| { - let (cid, status) = if sent { - let cid = faucet_tx_base_url - .get() - .as_ref() - .and_then(|base_url| { - format_url(base_url, SearchPath::Transaction, &msg.to_string()).ok() - }) - .map(|tx_url| { - view! { - - {msg.to_string()} - - } - .into_any() - }) - .unwrap_or_else(|| view! { {msg.to_string()} }.into_any()); - (cid, "(confirmed)") - } else { - let cid = view! { {msg.to_string()} }.into_any(); - (cid, "(pending)") - }; - view! {
  • "CID:" {cid} {status}
  • } - }) - .collect::>()} -
-
- } - .into_any() - } else { - ().into_any() - } - }} -
-
- {move || { - match faucet_tx_base_url.get() { - Some(ref base_url) => { - match format_url(base_url, SearchPath::Address, &faucet.get().get_sender_address()) { - Ok(addr_url) => { - view! { - - } - .into_any() - } - Err(_) => ().into_any(), - } - } - None => ().into_any(), - } - }} -
- } -} - -#[component] -pub fn Faucets() -> impl IntoView { - view! { - - <Meta name="description" content="Filecoin Faucet list" /> - <div class="text-center"> - <h1 class="text-4xl font-bold mb-6 text-center">Filecoin Faucet List</h1> - <a class="text-blue-600" href="/faucet/calibnet"> - Calibration Network Faucet - </a> - <br /> - <a class="text-blue-600" href="/faucet/mainnet"> - Mainnet Network Faucet - </a> - </div> - } -} - -#[component] -pub fn Faucet_Calibnet() -> impl IntoView { - let rpc_context = RpcContext::use_context(); - // Set rpc context to calibnet url - rpc_context.set(Provider::get_network_url(Network::Testnet)); - - view! { - <Title text="Filecoin Faucet - Calibration Network" /> - <Meta - name="description" - content="Filecoin Calibration Network Faucet dispensing tokens for testing purposes." - /> - <div> - <h1 class="text-4xl font-bold mb-6 text-center">Filecoin Calibnet Faucet</h1> - <Faucet target_network=Network::Testnet /> - </div> - <div class="text-center mt-4"> - "This faucet distributes " - {format_balance(&crate::constants::CALIBNET_DRIP_AMOUNT, crate::constants::FIL_CALIBNET_UNIT)} - " per request. It is rate-limited to 1 request per " {crate::constants::CALIBNET_RATE_LIMIT_SECONDS} - " seconds. Farming is discouraged and will result in more stringent rate limiting in the future and/or permanent bans." - </div> - } -} - -#[component] -pub fn Faucet_Mainnet() -> impl IntoView { - let rpc_context = RpcContext::use_context(); - // Set rpc context to mainnet url - rpc_context.set(Provider::get_network_url(Network::Mainnet)); - - view! { - <Title text="Filecoin Faucet - Mainnet" /> - <Meta name="description" content="Filecoin Mainnet Faucet dispensing tokens for testing purposes." /> - <div> - <h1 class="text-4xl font-bold mb-6 text-center">Filecoin Mainnet Faucet</h1> - <Faucet target_network=Network::Mainnet /> - <div class="text-center mt-4"> - "This faucet distributes " - {format_balance(&crate::constants::MAINNET_DRIP_AMOUNT, crate::constants::FIL_MAINNET_UNIT)} - " per request. It is rate-limited to 1 request per " {crate::constants::MAINNET_RATE_LIMIT_SECONDS} - " seconds. Farming is discouraged and will result in more stringent rate limiting in the future and/or permanent bans or service termination. Faucet funds are limited and may run out. They are replenished periodically." - </div> - </div> - } -} diff --git a/src/faucet/views/alert.rs b/src/faucet/views/alert.rs new file mode 100644 index 00000000..bdac086b --- /dev/null +++ b/src/faucet/views/alert.rs @@ -0,0 +1,77 @@ +use crate::faucet::FaucetController; +use leptos::prelude::*; +use leptos::task::spawn_local; +use leptos::{component, view, IntoView}; +use std::collections::HashSet; +use std::time::Duration; +use uuid::Uuid; + +const MESSAGE_FADE_AFTER: Duration = Duration::new(3, 0); +const MESSAGE_REMOVAL_AFTER: Duration = Duration::new(3, 500_000_000); + +#[component] +pub fn ErrorMessages( + errors: Vec<(Uuid, String)>, + faucet: RwSignal<FaucetController>, +) -> impl IntoView { + let (fading_messages, set_fading_messages) = signal(HashSet::new()); + view! { + <div class="fixed top-4 left-1/2 transform -translate-x-1/2 z-50"> + {errors + .into_iter() + .map(|(id, error)| { + spawn_local(async move { + set_timeout( + move || { + set_fading_messages + .update(|fading| { + fading.insert(id); + }); + }, + MESSAGE_FADE_AFTER, + ); + set_timeout( + move || { + set_fading_messages + .update(|fading| { + fading.remove(&id); + }); + faucet.get().remove_error_message(id); + }, + MESSAGE_REMOVAL_AFTER, + ); + }); + + view! { + <div + class=move || { + if fading_messages.get().contains(&id) { + "opacity-0 transition-opacity bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-2 w-96" + } else { + "bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-2 w-96" + } + } + role="alert" + > + <span class="block sm:inline">{error}</span> + <span class="absolute top-0 bottom-0 right-0 px-4 py-3"> + <svg + class="fill-current h-6 w-6 text-red-500" + role="button" + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 20 20" + on:click=move |_| { + faucet.get().remove_error_message(id); + } + > + <title>Close + + + + + } + }) + .collect::>()} + + } +} diff --git a/src/faucet/views/balance.rs b/src/faucet/views/balance.rs new file mode 100644 index 00000000..1cc28df0 --- /dev/null +++ b/src/faucet/views/balance.rs @@ -0,0 +1,55 @@ +use leptos::prelude::*; +use leptos::{component, view, IntoView}; + +use crate::faucet::FaucetController; +use crate::utils::format::format_balance; + +#[component] +pub fn FaucetBalance(faucet: RwSignal) -> impl IntoView { + view! { +
+

Faucet Balance:

+ Loading faucet balance...

} + }> + {move || { + if faucet.get().is_low_balance() { + let topup_req_url = option_env!("FAUCET_TOPUP_REQ_URL"); + view! { + + "Request Faucet Top-up" + + } + .into_any() + } else { + view! { +

+ {format_balance(&faucet.get().get_faucet_balance(), &faucet.get().get_fil_unit())} +

+ } + .into_any() + } + }} +
+
+ } +} + +#[component] +pub fn TargetBalance(faucet: RwSignal) -> impl IntoView { + view! { +
+

Target Balance:

+ Loading target balance...

}> +

+ {move || format_balance(&faucet.get().get_target_balance(), &faucet.get().get_fil_unit())} +

+
+
+ } +} diff --git a/src/faucet/views/faucet.rs b/src/faucet/views/faucet.rs new file mode 100644 index 00000000..7d064e7a --- /dev/null +++ b/src/faucet/views/faucet.rs @@ -0,0 +1,160 @@ +use fvm_shared::address::Network; +use leptos::prelude::*; +use leptos::{component, leptos_dom::helpers::event_target_value, view, IntoView}; +use leptos_meta::{Meta, Title}; +#[cfg(feature = "hydrate")] +use leptos_use::use_interval_fn; +use url::Url; + +use crate::faucet::views::alert::ErrorMessages; +use crate::faucet::views::balance::{FaucetBalance, TargetBalance}; +use crate::faucet::views::nav::GotoFaucetList; +use crate::faucet::views::transaction::{TransactionHistoryButton, TransactionList}; +use crate::faucet::FaucetController; + +#[component] +fn FaucetInput(faucet: RwSignal) -> impl IntoView { + view! { +
+ + {move || { + if faucet.get().is_send_disabled() { + view! { + + } + .into_any() + } else if faucet.get().get_send_rate_limit_remaining() > 0 { + let duration = faucet.get().get_send_rate_limit_remaining(); + view! { + + } + .into_any() + } else if faucet.get().is_low_balance() { + view! { + + } + .into_any() + } else { + view! { + + } + .into_any() + } + }} +
+ } +} + +#[cfg(feature = "hydrate")] +fn use_faucet_polling(faucet: RwSignal) { + let _ = use_interval_fn( + move || { + let duration = faucet.get().get_send_rate_limit_remaining(); + if duration > 0 { + faucet.get().set_send_rate_limit_remaining(duration - 1); + } + }, + 1000, + ); + + let _ = use_interval_fn( + move || { + faucet.get().refetch_balances(); + }, + 5000, + ); +} + +#[component] +pub fn Faucet(target_network: Network) -> impl IntoView { + let faucet = RwSignal::new(FaucetController::new(target_network)); + + #[cfg(feature = "hydrate")] + { + use_faucet_polling(faucet); + } + + let faucet_tx_base_url = match target_network { + Network::Mainnet => { + RwSignal::new(option_env!("FAUCET_TX_URL_MAINNET").and_then(|url| Url::parse(url).ok())) + } + Network::Testnet => RwSignal::new( + option_env!("FAUCET_TX_URL_CALIBNET").and_then(|url| Url::parse(url).ok()), + ), + }; + + view! { + {move || { + let errors = faucet.get().get_error_messages(); + if !errors.is_empty() { + view! { }.into_any() + } else { + ().into_any() + } + }} +
+ +
+ + +
+
+ {move || { + let messages = faucet.get().get_sent_messages(); + if !messages.is_empty() { + view! { }.into_any() + } else { + ().into_any() + } + }} +
+
+ + +
+ } +} + +#[component] +pub fn Faucets() -> impl IntoView { + view! { + + <Meta name="description" content="Filecoin Faucet list" /> + <div class="text-center"> + <h1 class="text-4xl font-bold mb-6 text-center">Filecoin Faucet List</h1> + <a class="text-blue-600" href="/faucet/calibnet"> + Calibration Network Faucet + </a> + <br /> + <a class="text-blue-600" href="/faucet/mainnet"> + Mainnet Network Faucet + </a> + </div> + } +} diff --git a/src/faucet/views/home.rs b/src/faucet/views/home.rs new file mode 100644 index 00000000..705928a4 --- /dev/null +++ b/src/faucet/views/home.rs @@ -0,0 +1,140 @@ +use crate::faucet::views::icons::{CheckIcon, LightningIcon, Loader}; +use crate::faucet::views::layout::Header; +use crate::faucet::views::nav::GotoFaucetList; +use crate::utils::rpc_context::{Provider, RpcContext}; +use fvm_shared::address::Network; +use leptos::prelude::*; +use leptos::{component, leptos_dom::helpers::event_target_value, view, IntoView}; + +#[component] +fn FaucetOverview() -> impl IntoView { + view! { + <div class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl w-full m-auto"> + <div class="bg-white p-6 rounded-lg border border-gray-100"> + <h2 class="text-lg font-semibold text-gray-900 mb-4">What does the faucet offer?</h2> + <ul class="space-y-3"> + <li class="flex items-start"> + <CheckIcon /> + <span class="text-gray-600">Free calibnet tFIL for experimentation and development.</span> + </li> + <li class="flex items-start"> + <CheckIcon /> + <span class="text-gray-600"> + Real mainnet FIL for contributors engaging with the Filecoin ecosystem. + </span> + </li> + <li class="flex items-start"> + <CheckIcon /> + <span class="text-gray-600"> + A Quick and Easy way to request free tFIL and FIL - Just enter your wallet address. + </span> + </li> + </ul> + </div> + + <div class="bg-white p-6 rounded-lg border border-gray-100"> + <h2 class="text-lg font-semibold text-gray-900 mb-4">Why use this faucet?</h2> + <ul class="space-y-3"> + <li class="flex items-start"> + <LightningIcon class="text-blue-500".to_string() /> + <span class="text-gray-600">Supports both calibnet and mainnet, unlike typical faucets.</span> + </li> + <li class="flex items-start"> + <LightningIcon class="text-blue-500".to_string() /> + <span class="text-gray-600"> + Enables testing of smart contracts, storage deals, and blockchain interactions without financial risk. + </span> + </li> + <li class="flex items-start"> + <LightningIcon class="text-blue-500".to_string() /> + <span class="text-gray-600"> + Easy access to FIL for developers and users building on Filecoin. + </span> + </li> + <li class="flex items-start"> + <LightningIcon class="text-blue-500".to_string() /> + <span class="text-gray-600"> + Need help? Visit the + <a class="text-blue-500" href="https://filecoin.io/slack" target="_blank"> + {" "} + Filecoin Slack + </a>{" "}or <a class="text-blue-500" href="https://docs.filecoin.io" target="_blank"> + {" "} + documentation + </a>. + </span> + </li> + </ul> + </div> + </div> + } +} + +#[component] +fn NetworkSelection( + rpc_context: RpcContext, + network_name: LocalResource<Option<String>>, + network_version: LocalResource<Option<u64>>, +) -> impl IntoView { + view! { + <div class="space-y-6 flex flex-col items-center"> + <div class="relative w-64"> + <select + on:change=move |ev| { rpc_context.set(event_target_value(&ev)) } + class="w-full px-4 py-2 text-sm text-gray-700 bg-white border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 hover:border-gray-400 transition-colors cursor-pointer appearance-none" + > + <option value=Provider::get_network_url(Network::Testnet)>Glif.io Calibnet</option> + <option value=Provider::get_network_url(Network::Mainnet)>Glif.io Mainnet</option> + </select> + <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"> + <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> + </svg> + </div> + </div> + + <div class="flex items-center w-[300px] justify-between"> + <p>Network:</p> + <Transition fallback=move || view! { <p>Loading network name...</p> }> + <p> + <span>{move || network_name.get().flatten()}</span> + <Loader loading=move || network_name.get().is_none() /> + </p> + </Transition> + </div> + + <div class="flex items-center w-[300px] justify-between"> + <p>Version:</p> + <Transition fallback=move || view! { <p>Loading network version...</p> }> + <p> + <span>{move || network_version.get().flatten()}</span> + <Loader loading=move || network_version.get().is_none() /> + </p> + </Transition> + </div> + </div> + } +} + +#[component] +pub fn Explorer() -> impl IntoView { + let rpc_context = RpcContext::use_context(); + let network_name = LocalResource::new(move || { + let provider = rpc_context.get(); + async move { provider.network_name().await.ok() } + }); + + let network_version = LocalResource::new(move || { + let provider = rpc_context.get(); + async move { provider.network_version().await.ok() } + }); + + view! { + <main class="min-h-screen flex flex-col flex-grow space-y-8"> + <Header /> + <FaucetOverview /> + <NetworkSelection rpc_context=rpc_context network_name=network_name network_version=network_version /> + <GotoFaucetList /> + </main> + } +} diff --git a/src/faucet/views/icons.rs b/src/faucet/views/icons.rs new file mode 100644 index 00000000..f7d5ec78 --- /dev/null +++ b/src/faucet/views/icons.rs @@ -0,0 +1,34 @@ +use leptos::prelude::*; + +#[component] +pub fn CheckIcon(#[prop(default = String::new())] class: String) -> impl IntoView { + view! { + <svg + class=format!("h-5 w-5 text-green-500 mr-2 flex-shrink-0 {}", class) + fill="none" + stroke="currentColor" + viewBox="0 0 24 24" + > + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /> + </svg> + } +} + +#[component] +pub fn LightningIcon(#[prop(default = String::new())] class: String) -> impl IntoView { + view! { + <svg + class=format!("h-5 w-5 text-blue-500 mr-2 flex-shrink-0 {}", class) + fill="none" + stroke="currentColor" + viewBox="0 0 24 24" + > + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /> + </svg> + } +} + +#[component] +pub fn Loader(loading: impl Fn() -> bool + 'static + Send) -> impl IntoView { + view! { <span class:loader=loading /> } +} diff --git a/src/faucet/views/layout.rs b/src/faucet/views/layout.rs new file mode 100644 index 00000000..b982140b --- /dev/null +++ b/src/faucet/views/layout.rs @@ -0,0 +1,36 @@ +use leptos::prelude::*; +use leptos::{component, view, IntoView}; + +#[component] +pub fn Header() -> impl IntoView { + view! { + <header class="space-y-6 flex flex-col items-center"> + <h1 class="text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl"> + Filecoin Forest Explorer Faucet + </h1> + <p class="max-w-2xl text-center"> + The Filecoin Forest Explorer Faucet provides developers and users with free calibnet(tFil) and mainnet(FIL) to support their exploration, testing and development on the Filecoin network. + </p> + </header> + } +} + +#[component] +pub fn Footer() -> impl IntoView { + view! { + <footer class="py-4 text-center"> + <a + class="text-green-600" + target="_blank" + rel="noopener noreferrer" + href="https://github.com/ChainSafe/forest-explorer" + > + Forest Explorer + </a> + ", built with ❤️ by " + <a class="text-blue-600" target="_blank" rel="noopener noreferrer" href="https://chainsafe.io"> + ChainSafe Systems + </a> + </footer> + } +} diff --git a/src/faucet/views/mod.rs b/src/faucet/views/mod.rs new file mode 100644 index 00000000..56548255 --- /dev/null +++ b/src/faucet/views/mod.rs @@ -0,0 +1,8 @@ +pub mod alert; +pub mod balance; +pub mod faucet; +pub mod home; +pub mod icons; +pub mod layout; +pub mod nav; +pub mod transaction; diff --git a/src/faucet/views/nav.rs b/src/faucet/views/nav.rs new file mode 100644 index 00000000..49b4d2a2 --- /dev/null +++ b/src/faucet/views/nav.rs @@ -0,0 +1,13 @@ +use leptos::prelude::*; +use leptos::{component, view, IntoView}; + +#[component] +pub fn GotoFaucetList() -> impl IntoView { + view! { + <div class="text-center"> + <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg"> + <a href="/faucet">View Faucet List</a> + </button> + </div> + } +} diff --git a/src/faucet/views/transaction.rs b/src/faucet/views/transaction.rs new file mode 100644 index 00000000..edfa4230 --- /dev/null +++ b/src/faucet/views/transaction.rs @@ -0,0 +1,81 @@ +use crate::utils::format::{format_url, SearchPath}; +use ::cid::Cid; +use leptos::prelude::*; +use leptos::{component, view, IntoView}; +use url::Url; + +use crate::faucet::FaucetController; + +#[component] +pub fn TransactionList( + messages: Vec<(Cid, bool)>, + faucet_tx_base_url: RwSignal<Option<Url>>, +) -> impl IntoView { + view! { + <div class="mt-4 space-y-2"> + <h3 class="text-lg font-semibold">Transactions:</h3> + <ul class="list-disc pl-5"> + {messages + .into_iter() + .map(|(msg, sent)| { + let (cid, status) = if sent { + let cid = faucet_tx_base_url + .get() + .as_ref() + .and_then(|base_url| { + format_url(base_url, SearchPath::Transaction, &msg.to_string()).ok() + }) + .map(|tx_url| { + view! { + <a + href=tx_url.to_string() + target="_blank" + class="text-blue-600 hover:underline" + > + {msg.to_string()} + </a> + } + .into_any() + }) + .unwrap_or_else(|| view! { {msg.to_string()} }.into_any()); + (cid, "(confirmed)") + } else { + let cid = view! { {msg.to_string()} }.into_any(); + (cid, "(pending)") + }; + view! { <li>"CID:" {cid} {status}</li> } + }) + .collect::<Vec<_>>()} + </ul> + </div> + } +} + +#[component] +pub fn TransactionHistoryButton( + faucet: RwSignal<FaucetController>, + faucet_tx_base_url: RwSignal<Option<Url>>, +) -> impl IntoView { + view! { + {move || { + match faucet_tx_base_url.get() { + Some(ref base_url) => { + match format_url(base_url, SearchPath::Address, &faucet.get().get_sender_address()) { + Ok(addr_url) => { + view! { + <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg"> + <a href=addr_url.to_string() target="_blank" rel="noopener noreferrer"> + "Transaction History" + </a> + </button> + } + .into_any() + } + Err(_) => ().into_any(), + } + } + None => ().into_any(), + } + }} + } +} diff --git a/src/icons/check_icon.rs b/src/icons/check_icon.rs deleted file mode 100644 index 50f228b3..00000000 --- a/src/icons/check_icon.rs +++ /dev/null @@ -1,15 +0,0 @@ -use leptos::prelude::*; - -#[component] -pub fn CheckIcon(#[prop(default = String::new())] class: String) -> impl IntoView { - view! { - <svg - class=format!("h-5 w-5 text-green-500 mr-2 flex-shrink-0 {}", class) - fill="none" - stroke="currentColor" - viewBox="0 0 24 24" - > - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /> - </svg> - } -} diff --git a/src/icons/lightning_icon.rs b/src/icons/lightning_icon.rs deleted file mode 100644 index 4eacc6fc..00000000 --- a/src/icons/lightning_icon.rs +++ /dev/null @@ -1,15 +0,0 @@ -use leptos::prelude::*; - -#[component] -pub fn LightningIcon(#[prop(default = String::new())] class: String) -> impl IntoView { - view! { - <svg - class=format!("h-5 w-5 text-blue-500 mr-2 flex-shrink-0 {}", class) - fill="none" - stroke="currentColor" - viewBox="0 0 24 24" - > - <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /> - </svg> - } -} diff --git a/src/icons/mod.rs b/src/icons/mod.rs deleted file mode 100644 index 431eef02..00000000 --- a/src/icons/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod check_icon; -mod lightning_icon; - -pub use check_icon::CheckIcon; -pub use lightning_icon::LightningIcon; diff --git a/src/lib.rs b/src/lib.rs index 1253376f..520857dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,8 @@ mod app; -mod rpc_context; +mod utils; #[cfg(feature = "hydrate")] use app::App; -mod address; -mod constants; mod faucet; -mod icons; -mod key; -mod lotus_json; -mod message; -#[cfg(feature = "ssr")] -mod rate_limiter; -mod utils; #[cfg(feature = "hydrate")] #[wasm_bindgen::prelude::wasm_bindgen] @@ -25,15 +16,35 @@ pub fn hydrate() { mod ssr_imports { use std::sync::Arc; - use crate::{ - app::{shell, App}, - faucet, - }; + use crate::{app::App, faucet}; use axum::{routing::post, Extension, Router}; use leptos::prelude::*; use leptos_axum::{generate_route_list, LeptosRoutes}; + use leptos_meta::*; use worker::{event, Context, Env, HttpRequest, Result}; + fn shell(options: LeptosOptions) -> impl IntoView { + view! { + <!DOCTYPE html> + <html lang="en"> + <head> + <title>Filecoin Forest Explorer Faucet - Get Free tFIL and FIL + + + + + + + + + + + } + } + fn router(env: Env) -> Router { let leptos_options = LeptosOptions::builder() .output_name("client") @@ -55,8 +66,8 @@ mod ssr_imports { #[event(start)] fn register() { - server_fn::axum::register_explicit::(); - server_fn::axum::register_explicit::(); + server_fn::axum::register_explicit::(); + server_fn::axum::register_explicit::(); } #[event(fetch)] diff --git a/src/lotus_json/signed_message.rs b/src/lotus_json/signed_message.rs deleted file mode 100644 index ca889e17..00000000 --- a/src/lotus_json/signed_message.rs +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2019-2024 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - -use crate::message::{Message, SignedMessage}; -use ::cid::Cid; -use fvm_shared::crypto::signature::Signature; - -use super::*; - -#[derive(Clone, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] -pub struct SignedMessageLotusJson { - #[serde(with = "crate::lotus_json")] - message: Message, - #[serde(with = "crate::lotus_json")] - signature: Signature, - #[serde( - with = "crate::lotus_json", - rename = "CID", - skip_serializing_if = "Option::is_none", - default - )] - cid: Option, -} - -impl HasLotusJson for SignedMessage { - type LotusJson = SignedMessageLotusJson; - - fn into_lotus_json(self) -> Self::LotusJson { - let cid = Some(self.cid()); - let Self { message, signature } = self; - Self::LotusJson { - message, - signature, - cid, - } - } - - fn from_lotus_json(lotus_json: Self::LotusJson) -> Self { - let Self::LotusJson { - message, - signature, - cid: _ignored, // See notes on Message - } = lotus_json; - Self { message, signature } - } -} diff --git a/src/message.rs b/src/message.rs deleted file mode 100644 index a36b954d..00000000 --- a/src/message.rs +++ /dev/null @@ -1,63 +0,0 @@ -use cid::Cid; -use fvm_ipld_encoding::Error; -use fvm_ipld_encoding::RawBytes; -pub use fvm_shared::message::Message; -use fvm_shared::{ - address::Address, - crypto::signature::{Signature, SignatureType}, - econ::TokenAmount, - METHOD_SEND, -}; -use multihash_codetable::{Code, MultihashDigest as _}; -use serde::{Deserialize, Serialize}; - -fn from_cbor_blake2b256(obj: &S) -> Result { - let bytes = fvm_ipld_encoding::to_vec(obj)?; - Ok(Cid::new_v1( - fvm_ipld_encoding::DAG_CBOR, - Code::Blake2b256.digest(&bytes), - )) -} - -pub fn message_transfer(from: Address, to: Address, value: TokenAmount) -> Message { - Message { - from, - to, - value, - method_num: METHOD_SEND, - params: RawBytes::new(vec![]), - gas_limit: 0, - gas_fee_cap: TokenAmount::from_atto(0), - gas_premium: TokenAmount::from_atto(0), - version: 0, - sequence: 0, - } -} - -pub fn message_cid(msg: &Message) -> cid::Cid { - from_cbor_blake2b256(msg).expect("message serialization is infallible") -} - -#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash, Eq)] -pub struct SignedMessage { - pub message: Message, - pub signature: Signature, -} - -impl SignedMessage { - /// Checks if the signed message is a BLS message. - pub fn is_bls(&self) -> bool { - self.signature.signature_type() == SignatureType::BLS - } - - // Important note: `msg.cid()` is different from - // `Cid::from_cbor_blake2b256(msg)`. The behavior comes from Lotus, and - // Lotus, by, definition, is correct. - pub fn cid(&self) -> cid::Cid { - if self.is_bls() { - message_cid(&self.message) - } else { - from_cbor_blake2b256(self).expect("message serialization is infallible") - } - } -} diff --git a/src/address.rs b/src/utils/address.rs similarity index 100% rename from src/address.rs rename to src/utils/address.rs diff --git a/src/utils.rs b/src/utils/error.rs similarity index 99% rename from src/utils.rs rename to src/utils/error.rs index 660555d8..a0b96f12 100644 --- a/src/utils.rs +++ b/src/utils/error.rs @@ -1,6 +1,5 @@ -use std::future::Future; - use leptos::prelude::{RwSignal, Update}; +use std::future::Future; use uuid::Uuid; pub async fn catch_all( diff --git a/src/utils/format.rs b/src/utils/format.rs new file mode 100644 index 00000000..3844633e --- /dev/null +++ b/src/utils/format.rs @@ -0,0 +1,76 @@ +use anyhow::{anyhow, Result}; +use fvm_shared::econ::TokenAmount; +use url::Url; + +/// Formats FIL balance to a human-readable string with two decimal places and a unit. +pub fn format_balance(balance: &TokenAmount, unit: &str) -> String { + format!( + "{:.2} {unit}", + balance.to_string().parse::().unwrap_or_default(), + ) +} + +/// Types of search paths in Filecoin explorer. +#[derive(Copy, Clone)] +pub enum SearchPath { + Transaction, + Address, +} + +impl SearchPath { + pub fn as_str(&self) -> &'static str { + match self { + SearchPath::Transaction => "txs/", + SearchPath::Address => "address/", + } + } +} + +/// Constructs a URL combining base URL, search path, and an identifier. +pub fn format_url(base_url: &Url, path: SearchPath, identifier: &str) -> Result { + base_url + .join(path.as_str())? + .join(identifier) + .map_err(|e| anyhow!("Failed to join URL: {}", e)) +} + +#[cfg(test)] +mod tests { + use super::*; + use fvm_shared::econ::TokenAmount; + + #[test] + fn test_format_balance() { + let cases = [ + (TokenAmount::from_whole(1), "1.00 FIL"), + (TokenAmount::from_whole(0), "0.00 FIL"), + (TokenAmount::from_nano(10e6 as i64), "0.01 FIL"), + (TokenAmount::from_nano(999_999_999), "1.00 FIL"), + ]; + for (balance, expected) in cases.iter() { + assert_eq!(format_balance(balance, "FIL"), *expected); + } + } + + #[test] + fn test_format_url() { + let base = Url::parse("https://test.com/").unwrap(); + let cases = [ + ( + SearchPath::Transaction, + "0xdef456", + "https://test.com/txs/0xdef456", + ), + ( + SearchPath::Address, + "0xabc123", + "https://test.com/address/0xabc123", + ), + ]; + + for (path, query, expected) in cases.iter() { + let result = format_url(&base, *path, query).unwrap(); + assert_eq!(result.as_str(), *expected); + } + } +} diff --git a/src/key.rs b/src/utils/key.rs similarity index 100% rename from src/key.rs rename to src/utils/key.rs diff --git a/src/lotus_json/address.rs b/src/utils/lotus_json/address.rs similarity index 100% rename from src/lotus_json/address.rs rename to src/utils/lotus_json/address.rs diff --git a/src/lotus_json/big_int.rs b/src/utils/lotus_json/big_int.rs similarity index 82% rename from src/lotus_json/big_int.rs rename to src/utils/lotus_json/big_int.rs index a7236dae..78eba89c 100644 --- a/src/lotus_json/big_int.rs +++ b/src/utils/lotus_json/big_int.rs @@ -6,7 +6,7 @@ use super::*; use fvm_shared::bigint::BigInt; #[derive(Clone, Serialize, Deserialize)] -pub struct BigIntLotusJson(#[serde(with = "crate::lotus_json::stringify")] BigInt); +pub struct BigIntLotusJson(#[serde(with = "crate::utils::lotus_json::stringify")] BigInt); impl HasLotusJson for BigInt { type LotusJson = BigIntLotusJson; diff --git a/src/lotus_json/cid.rs b/src/utils/lotus_json/cid.rs similarity index 87% rename from src/lotus_json/cid.rs rename to src/utils/lotus_json/cid.rs index 69c6bdad..14127d08 100644 --- a/src/lotus_json/cid.rs +++ b/src/utils/lotus_json/cid.rs @@ -5,7 +5,7 @@ use super::*; #[derive(Clone, Serialize, Deserialize)] pub struct CidLotusJson { - #[serde(rename = "/", with = "crate::lotus_json::stringify")] + #[serde(rename = "/", with = "crate::utils::lotus_json::stringify")] slash: ::cid::Cid, } diff --git a/src/lotus_json/message.rs b/src/utils/lotus_json/message.rs similarity index 83% rename from src/lotus_json/message.rs rename to src/utils/lotus_json/message.rs index 4d968a30..12a67702 100644 --- a/src/lotus_json/message.rs +++ b/src/utils/lotus_json/message.rs @@ -2,34 +2,32 @@ // SPDX-License-Identifier: Apache-2.0, MIT use super::*; - -use crate::message::Message; use fvm_ipld_encoding::RawBytes; -use fvm_shared::{address::Address, econ::TokenAmount}; +use fvm_shared::{address::Address, econ::TokenAmount, message::Message}; #[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct MessageLotusJson { #[serde(default)] version: u64, - #[serde(with = "crate::lotus_json")] + #[serde(with = "crate::utils::lotus_json")] to: Address, - #[serde(with = "crate::lotus_json")] + #[serde(with = "crate::utils::lotus_json")] from: Address, #[serde(default)] nonce: u64, - #[serde(with = "crate::lotus_json", default)] + #[serde(with = "crate::utils::lotus_json", default)] value: TokenAmount, #[serde(default)] gas_limit: u64, - #[serde(with = "crate::lotus_json", default)] + #[serde(with = "crate::utils::lotus_json", default)] gas_fee_cap: TokenAmount, - #[serde(with = "crate::lotus_json", default)] + #[serde(with = "crate::utils::lotus_json", default)] gas_premium: TokenAmount, #[serde(default)] method: u64, #[serde( - with = "crate::lotus_json", + with = "crate::utils::lotus_json", skip_serializing_if = "Option::is_none", default )] diff --git a/src/lotus_json/mod.rs b/src/utils/lotus_json/mod.rs similarity index 99% rename from src/lotus_json/mod.rs rename to src/utils/lotus_json/mod.rs index 3376535a..b2fe31c4 100644 --- a/src/lotus_json/mod.rs +++ b/src/utils/lotus_json/mod.rs @@ -169,11 +169,12 @@ mod message; mod opt; mod signature; mod signature_type; -mod signed_message; mod token_amount; mod vec; mod vec_u8; +pub mod signed_message; + // mod nonempty; // can't make snapshots of generic type // mod opt; // can't make snapshots of generic type mod raw_bytes; // fvm_ipld_encoding::RawBytes: !quickcheck::Arbitrary @@ -185,7 +186,7 @@ mod raw_bytes; // fvm_ipld_encoding::RawBytes: !quickcheck::Arbitrary #[serde(rename_all = "PascalCase")] pub struct MessageLookup { pub height: i64, - #[serde(with = "crate::lotus_json")] + #[serde(with = "crate::utils::lotus_json")] pub message: Cid, } lotus_json_with_self!(MessageLookup); @@ -361,7 +362,7 @@ impl LotusJson { macro_rules! lotus_json_with_self { ($($domain_ty:ty),* $(,)?) => { $( - impl $crate::lotus_json::HasLotusJson for $domain_ty { + impl $crate::utils::lotus_json::HasLotusJson for $domain_ty { type LotusJson = Self; fn into_lotus_json(self) -> Self::LotusJson { self diff --git a/src/lotus_json/opt.rs b/src/utils/lotus_json/opt.rs similarity index 100% rename from src/lotus_json/opt.rs rename to src/utils/lotus_json/opt.rs diff --git a/src/lotus_json/raw_bytes.rs b/src/utils/lotus_json/raw_bytes.rs similarity index 100% rename from src/lotus_json/raw_bytes.rs rename to src/utils/lotus_json/raw_bytes.rs diff --git a/src/lotus_json/signature.rs b/src/utils/lotus_json/signature.rs similarity index 89% rename from src/lotus_json/signature.rs rename to src/utils/lotus_json/signature.rs index 9f301ce6..c7d6a8c1 100644 --- a/src/lotus_json/signature.rs +++ b/src/utils/lotus_json/signature.rs @@ -7,9 +7,9 @@ use fvm_shared::crypto::signature::{Signature, SignatureType}; #[derive(Clone, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct SignatureLotusJson { - #[serde(with = "crate::lotus_json")] + #[serde(with = "crate::utils::lotus_json")] r#type: SignatureType, - #[serde(with = "crate::lotus_json")] + #[serde(with = "crate::utils::lotus_json")] data: Vec, } diff --git a/src/lotus_json/signature_type.rs b/src/utils/lotus_json/signature_type.rs similarity index 93% rename from src/lotus_json/signature_type.rs rename to src/utils/lotus_json/signature_type.rs index b528fac7..0b3a79b4 100644 --- a/src/lotus_json/signature_type.rs +++ b/src/utils/lotus_json/signature_type.rs @@ -15,7 +15,7 @@ use fvm_shared::crypto::signature::SignatureType; #[serde(untagged)] // try an int, then a string pub enum SignatureTypeLotusJson { Integer(SignatureType), - // String(#[serde(with = "crate::lotus_json::stringify")] SignatureType), + // String(#[serde(with = "crate::utils::lotus_json::stringify")] SignatureType), } impl HasLotusJson for SignatureType { diff --git a/src/utils/lotus_json/signed_message.rs b/src/utils/lotus_json/signed_message.rs new file mode 100644 index 00000000..f5c4f3fe --- /dev/null +++ b/src/utils/lotus_json/signed_message.rs @@ -0,0 +1,85 @@ +// Copyright 2019-2024 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +use super::HasLotusJson; +use ::cid::Cid; +use fvm_ipld_encoding::Error; +use fvm_shared::crypto::signature::Signature; +use fvm_shared::crypto::signature::SignatureType; +use fvm_shared::message::Message; +use multihash_codetable::{Code, MultihashDigest as _}; +use serde::{Deserialize, Serialize}; + +#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash, Eq)] +pub struct SignedMessage { + pub message: Message, + pub signature: Signature, +} + +fn from_cbor_blake2b256(obj: &S) -> Result { + let bytes = fvm_ipld_encoding::to_vec(obj)?; + Ok(Cid::new_v1( + fvm_ipld_encoding::DAG_CBOR, + Code::Blake2b256.digest(&bytes), + )) +} +pub fn message_cid(msg: &Message) -> Cid { + from_cbor_blake2b256(msg).expect("message serialization is infallible") +} + +impl SignedMessage { + /// Checks if the signed message is a BLS message. + pub fn is_bls(&self) -> bool { + self.signature.signature_type() == SignatureType::BLS + } + + // Important note: `msg.cid()` is different from + // `Cid::from_cbor_blake2b256(msg)`. The behavior comes from Lotus, and + // Lotus, by, definition, is correct. + pub fn cid(&self) -> Cid { + if self.is_bls() { + message_cid(&self.message) + } else { + from_cbor_blake2b256(self).expect("message serialization is infallible") + } + } +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct SignedMessageLotusJson { + #[serde(with = "crate::utils::lotus_json")] + message: Message, + #[serde(with = "crate::utils::lotus_json")] + signature: Signature, + #[serde( + with = "crate::utils::lotus_json", + rename = "CID", + skip_serializing_if = "Option::is_none", + default + )] + cid: Option, +} + +impl HasLotusJson for SignedMessage { + type LotusJson = SignedMessageLotusJson; + + fn into_lotus_json(self) -> Self::LotusJson { + let cid = Some(self.cid()); + let Self { message, signature } = self; + Self::LotusJson { + message, + signature, + cid, + } + } + + fn from_lotus_json(lotus_json: Self::LotusJson) -> Self { + let Self::LotusJson { + message, + signature, + cid: _ignored, // See notes on Message + } = lotus_json; + Self { message, signature } + } +} diff --git a/src/lotus_json/token_amount.rs b/src/utils/lotus_json/token_amount.rs similarity index 93% rename from src/lotus_json/token_amount.rs rename to src/utils/lotus_json/token_amount.rs index d300a7ff..c9a5d075 100644 --- a/src/lotus_json/token_amount.rs +++ b/src/utils/lotus_json/token_amount.rs @@ -8,7 +8,7 @@ use fvm_shared::econ::TokenAmount; #[derive(Clone, Serialize, Deserialize)] #[serde(transparent)] // name the field for clarity pub struct TokenAmountLotusJson { - #[serde(with = "crate::lotus_json")] + #[serde(with = "crate::utils::lotus_json")] attos: BigInt, } diff --git a/src/lotus_json/vec.rs b/src/utils/lotus_json/vec.rs similarity index 100% rename from src/lotus_json/vec.rs rename to src/utils/lotus_json/vec.rs diff --git a/src/lotus_json/vec_u8.rs b/src/utils/lotus_json/vec_u8.rs similarity index 100% rename from src/lotus_json/vec_u8.rs rename to src/utils/lotus_json/vec_u8.rs diff --git a/src/utils/message.rs b/src/utils/message.rs new file mode 100644 index 00000000..74452cce --- /dev/null +++ b/src/utils/message.rs @@ -0,0 +1,18 @@ +use fvm_ipld_encoding::RawBytes; +use fvm_shared::message::Message; +use fvm_shared::{address::Address, econ::TokenAmount, METHOD_SEND}; + +pub fn message_transfer(from: Address, to: Address, value: TokenAmount) -> Message { + Message { + from, + to, + value, + method_num: METHOD_SEND, + params: RawBytes::new(vec![]), + gas_limit: 0, + gas_fee_cap: TokenAmount::from_atto(0), + gas_premium: TokenAmount::from_atto(0), + version: 0, + sequence: 0, + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 00000000..ef28000c --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,7 @@ +pub mod address; +pub mod error; +pub mod format; +pub mod key; +pub mod lotus_json; +pub mod message; +pub mod rpc_context; diff --git a/src/rpc_context.rs b/src/utils/rpc_context.rs similarity index 96% rename from src/rpc_context.rs rename to src/utils/rpc_context.rs index cf15bd2f..7ba72b9b 100644 --- a/src/rpc_context.rs +++ b/src/utils/rpc_context.rs @@ -7,8 +7,7 @@ use reqwest::Client; use serde_json::{json, Value}; use std::sync::LazyLock; -use crate::lotus_json::{HasLotusJson, LotusJson}; -use crate::message::SignedMessage; +use super::lotus_json::{signed_message::SignedMessage, HasLotusJson, LotusJson}; static CLIENT: LazyLock = LazyLock::new(Client::new); @@ -173,7 +172,7 @@ impl Provider { pub async fn state_search_msg( &self, msg: Cid, - ) -> anyhow::Result> { + ) -> anyhow::Result> { invoke_rpc_method( &self.url, "Filecoin.StateSearchMsg", From 744a5793414ef0da18f8f50f3be5f1a0dc4cddd1 Mon Sep 17 00:00:00 2001 From: Shashank Date: Tue, 27 May 2025 10:33:44 +0530 Subject: [PATCH 2/9] Add home button and change faucet list button name --- e2e/script.js | 7 ++++--- src/faucet/views/faucet.rs | 5 +++-- src/faucet/views/nav.rs | 13 ++++++++++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/e2e/script.js b/e2e/script.js index 703b426d..04402a47 100644 --- a/e2e/script.js +++ b/e2e/script.js @@ -104,20 +104,21 @@ async function checkFooter(page, path) { const PAGES = [ { path: "", - buttons: ["To faucet list"], + buttons: ["Faucet List"], links: ["Filecoin Slack", "documentation"], }, { path: "/faucet", + buttons: ["Home"], links: ["Calibration Network Faucet", "Mainnet Network Faucet"], }, { path: "/faucet/calibnet", - buttons: ["Back to faucet list", "Transaction History", "Send"], + buttons: ["Faucet List", "Transaction History", "Send"], }, { path: "/faucet/mainnet", - buttons: ["Back to faucet list", "Transaction History", "Send"], + buttons: ["Faucet List", "Transaction History", "Send"], }, ]; diff --git a/src/faucet/views/faucet.rs b/src/faucet/views/faucet.rs index 7d064e7a..220182f7 100644 --- a/src/faucet/views/faucet.rs +++ b/src/faucet/views/faucet.rs @@ -8,7 +8,7 @@ use url::Url; use crate::faucet::views::alert::ErrorMessages; use crate::faucet::views::balance::{FaucetBalance, TargetBalance}; -use crate::faucet::views::nav::GotoFaucetList; +use crate::faucet::views::nav::{GotoFaucetList, GotoHome}; use crate::faucet::views::transaction::{TransactionHistoryButton, TransactionList}; use crate::faucet::FaucetController; @@ -146,7 +146,7 @@ pub fn Faucets() -> impl IntoView { view! { <Meta name="description" content="Filecoin Faucet list" /> - <div class="text-center"> + <div class="text-center space-y-8"> <h1 class="text-4xl font-bold mb-6 text-center">Filecoin Faucet List</h1> <a class="text-blue-600" href="/faucet/calibnet"> Calibration Network Faucet @@ -155,6 +155,7 @@ pub fn Faucets() -> impl IntoView { <a class="text-blue-600" href="/faucet/mainnet"> Mainnet Network Faucet </a> + <GotoHome /> </div> } } diff --git a/src/faucet/views/nav.rs b/src/faucet/views/nav.rs index 49b4d2a2..3ea18f5a 100644 --- a/src/faucet/views/nav.rs +++ b/src/faucet/views/nav.rs @@ -6,7 +6,18 @@ pub fn GotoFaucetList() -> impl IntoView { view! { <div class="text-center"> <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg"> - <a href="/faucet">View Faucet List</a> + <a href="/faucet">Faucet List</a> + </button> + </div> + } +} + +#[component] +pub fn GotoHome() -> impl IntoView { + view! { + <div class="text-center"> + <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg"> + <a href="/">Home</a> </button> </div> } From 79bf97d9569bfec847088c3d1f0de8db305cb1f9 Mon Sep 17 00:00:00 2001 From: Shashank <shashank@chainsafe.io> Date: Tue, 27 May 2025 16:08:38 +0530 Subject: [PATCH 3/9] Add license check --- Makefile | 6 +++++- scripts/add_license.sh | 19 +++++++++++++++++++ scripts/copyright.txt | 2 ++ src/app.rs | 3 +++ src/faucet/calibnet/mod.rs | 3 +++ src/faucet/calibnet/views.rs | 3 +++ src/faucet/mainnet/mod.rs | 3 +++ src/faucet/mainnet/views.rs | 3 +++ src/faucet/mod.rs | 19 +++++-------------- src/faucet/model.rs | 21 +++++++++++++++++++++ src/faucet/rate_limiter.rs | 3 +++ src/faucet/server.rs | 3 +++ src/faucet/views/alert.rs | 3 +++ src/faucet/views/balance.rs | 3 +++ src/faucet/views/faucet.rs | 3 +++ src/faucet/views/home.rs | 3 +++ src/faucet/views/icons.rs | 3 +++ src/faucet/views/layout.rs | 3 +++ src/faucet/views/mod.rs | 3 +++ src/faucet/views/nav.rs | 3 +++ src/faucet/views/transaction.rs | 3 +++ src/lib.rs | 3 +++ src/utils/address.rs | 5 ++++- src/utils/error.rs | 3 +++ src/utils/format.rs | 3 +++ src/utils/key.rs | 3 +++ src/utils/lotus_json/address.rs | 2 +- src/utils/lotus_json/big_int.rs | 2 +- src/utils/lotus_json/cid.rs | 2 +- src/utils/lotus_json/message.rs | 2 +- src/utils/lotus_json/mod.rs | 2 +- src/utils/lotus_json/opt.rs | 2 +- src/utils/lotus_json/raw_bytes.rs | 2 +- src/utils/lotus_json/signature.rs | 2 +- src/utils/lotus_json/signature_type.rs | 2 +- src/utils/lotus_json/signed_message.rs | 2 +- src/utils/lotus_json/token_amount.rs | 2 +- src/utils/lotus_json/vec.rs | 2 +- src/utils/lotus_json/vec_u8.rs | 2 +- src/utils/message.rs | 3 +++ src/utils/mod.rs | 3 +++ src/utils/rpc_context.rs | 3 +++ 42 files changed, 138 insertions(+), 29 deletions(-) create mode 100755 scripts/add_license.sh create mode 100644 scripts/copyright.txt create mode 100644 src/faucet/model.rs diff --git a/Makefile b/Makefile index 8c504a2c..6eac2cb9 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ install-lint-tools-ci: cp cargo-binstall ~/.cargo/bin/cargo-binstall cargo +stable binstall --no-confirm cargo-spellcheck taplo-cli cargo-deny leptosfmt -lint-all: deny spellcheck fmt-lints cargo-clippy +lint-all: deny spellcheck fmt-lints cargo-clippy license fmt: cargo fmt --all @@ -41,3 +41,7 @@ deny: spellcheck: cargo spellcheck --code 1 || (echo "See .config/spellcheck.toml"; false) + +# Checks if all headers are present and adds if not +license: + ./scripts/add_license.sh diff --git a/scripts/add_license.sh b/scripts/add_license.sh new file mode 100755 index 00000000..5497b87a --- /dev/null +++ b/scripts/add_license.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Checks if the source code contains required license and adds it if necessary. +# Returns 1 if there was a missing license, 0 otherwise. + +PAT_APA="^// Copyright 2019-2025 ChainSafe Systems// SPDX-License-Identifier: Apache-2.0, MIT$" + +ret=0 +for file in $(git grep --cached -Il '' -- '*.rs'); do + header=$(head -2 "$file" | tr -d '\n') + if ! echo "$header" | grep -q "$PAT_APA"; then + echo "$file was missing header" + cat ./scripts/copyright.txt "$file" > temp + mv temp "$file" + ret=1 + fi +done + +exit $ret diff --git a/scripts/copyright.txt b/scripts/copyright.txt new file mode 100644 index 00000000..babf0bf4 --- /dev/null +++ b/scripts/copyright.txt @@ -0,0 +1,2 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT diff --git a/src/app.rs b/src/app.rs index 4cb140d9..1ff280bb 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use crate::faucet::views::{faucet::Faucets, home::Explorer, layout::Footer}; use crate::faucet::{calibnet::views::Faucet_Calibnet, mainnet::views::Faucet_Mainnet}; use crate::utils::rpc_context::RpcContext; diff --git a/src/faucet/calibnet/mod.rs b/src/faucet/calibnet/mod.rs index 68797b78..79957e4f 100644 --- a/src/faucet/calibnet/mod.rs +++ b/src/faucet/calibnet/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + pub mod views; use fvm_shared::econ::TokenAmount; diff --git a/src/faucet/calibnet/views.rs b/src/faucet/calibnet/views.rs index 8793732f..a2e88157 100644 --- a/src/faucet/calibnet/views.rs +++ b/src/faucet/calibnet/views.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use crate::faucet::calibnet::{ CALIBNET_DRIP_AMOUNT, CALIBNET_RATE_LIMIT_SECONDS, FIL_CALIBNET_UNIT, }; diff --git a/src/faucet/mainnet/mod.rs b/src/faucet/mainnet/mod.rs index 17ab989c..10d6313d 100644 --- a/src/faucet/mainnet/mod.rs +++ b/src/faucet/mainnet/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + pub mod views; use fvm_shared::econ::TokenAmount; diff --git a/src/faucet/mainnet/views.rs b/src/faucet/mainnet/views.rs index de3acae5..4333e8f4 100644 --- a/src/faucet/mainnet/views.rs +++ b/src/faucet/mainnet/views.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use crate::faucet::mainnet::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS}; use crate::faucet::views::faucet::Faucet; use crate::utils::format::format_balance; diff --git a/src/faucet/mod.rs b/src/faucet/mod.rs index 1cab0ab9..bd0caebb 100644 --- a/src/faucet/mod.rs +++ b/src/faucet/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + #[cfg(feature = "ssr")] mod rate_limiter; @@ -5,6 +8,7 @@ pub mod calibnet; pub mod mainnet; pub mod server; pub mod views; +mod model; use calibnet::{CALIBNET_DRIP_AMOUNT, CALIBNET_RATE_LIMIT_SECONDS, FIL_CALIBNET_UNIT}; use mainnet::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS}; @@ -12,6 +16,7 @@ use mainnet::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS} use crate::utils::lotus_json::LotusJson; use crate::utils::rpc_context::Provider; use crate::utils::{address::parse_address, error::catch_all, message::message_transfer}; +use crate::faucet::model::FaucetModel; use cid::Cid; use fvm_shared::{address::Network, econ::TokenAmount}; use leptos::prelude::*; @@ -19,20 +24,6 @@ use leptos::task::spawn_local; use server::{faucet_address, sign_with_secret_key}; use uuid::Uuid; -#[derive(Clone)] -pub(super) struct FaucetModel { - pub network: Network, - pub send_disabled: RwSignal<bool>, - pub send_limited: RwSignal<i32>, - pub sent_messages: RwSignal<Vec<(Cid, bool)>>, - pub error_messages: RwSignal<Vec<(Uuid, String)>>, - pub balance_trigger: Trigger, - pub faucet_balance: LocalResource<TokenAmount>, - pub target_balance: LocalResource<TokenAmount>, - pub sender_address: RwSignal<String>, - pub target_address: RwSignal<String>, -} - #[derive(Clone)] pub(super) struct FaucetController { faucet: FaucetModel, diff --git a/src/faucet/model.rs b/src/faucet/model.rs new file mode 100644 index 00000000..dbb46657 --- /dev/null +++ b/src/faucet/model.rs @@ -0,0 +1,21 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +use cid::Cid; +use fvm_shared::{address::Network, econ::TokenAmount}; +use leptos::prelude::*; +use uuid::Uuid; + +#[derive(Clone)] +pub(super) struct FaucetModel { + pub network: Network, + pub send_disabled: RwSignal<bool>, + pub send_limited: RwSignal<i32>, + pub sent_messages: RwSignal<Vec<(Cid, bool)>>, + pub error_messages: RwSignal<Vec<(Uuid, String)>>, + pub balance_trigger: Trigger, + pub faucet_balance: LocalResource<TokenAmount>, + pub target_balance: LocalResource<TokenAmount>, + pub sender_address: RwSignal<String>, + pub target_address: RwSignal<String>, +} \ No newline at end of file diff --git a/src/faucet/rate_limiter.rs b/src/faucet/rate_limiter.rs index aa9355e7..f00b1069 100644 --- a/src/faucet/rate_limiter.rs +++ b/src/faucet/rate_limiter.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use crate::faucet::calibnet::CALIBNET_RATE_LIMIT_SECONDS; use crate::faucet::mainnet::MAINNET_RATE_LIMIT_SECONDS; use chrono::{DateTime, Duration, Utc}; diff --git a/src/faucet/server.rs b/src/faucet/server.rs index 845c7b00..ea8a814e 100644 --- a/src/faucet/server.rs +++ b/src/faucet/server.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + #[cfg(feature = "ssr")] use crate::utils::key::{sign, Key}; use crate::utils::lotus_json::{signed_message::SignedMessage, LotusJson}; diff --git a/src/faucet/views/alert.rs b/src/faucet/views/alert.rs index bdac086b..d6e9a757 100644 --- a/src/faucet/views/alert.rs +++ b/src/faucet/views/alert.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use crate::faucet::FaucetController; use leptos::prelude::*; use leptos::task::spawn_local; diff --git a/src/faucet/views/balance.rs b/src/faucet/views/balance.rs index 1cc28df0..a54b034f 100644 --- a/src/faucet/views/balance.rs +++ b/src/faucet/views/balance.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use leptos::prelude::*; use leptos::{component, view, IntoView}; diff --git a/src/faucet/views/faucet.rs b/src/faucet/views/faucet.rs index 220182f7..bf44af5b 100644 --- a/src/faucet/views/faucet.rs +++ b/src/faucet/views/faucet.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use fvm_shared::address::Network; use leptos::prelude::*; use leptos::{component, leptos_dom::helpers::event_target_value, view, IntoView}; diff --git a/src/faucet/views/home.rs b/src/faucet/views/home.rs index 705928a4..bc756316 100644 --- a/src/faucet/views/home.rs +++ b/src/faucet/views/home.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use crate::faucet::views::icons::{CheckIcon, LightningIcon, Loader}; use crate::faucet::views::layout::Header; use crate::faucet::views::nav::GotoFaucetList; diff --git a/src/faucet/views/icons.rs b/src/faucet/views/icons.rs index f7d5ec78..7aaed8f4 100644 --- a/src/faucet/views/icons.rs +++ b/src/faucet/views/icons.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use leptos::prelude::*; #[component] diff --git a/src/faucet/views/layout.rs b/src/faucet/views/layout.rs index b982140b..fcde9afd 100644 --- a/src/faucet/views/layout.rs +++ b/src/faucet/views/layout.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use leptos::prelude::*; use leptos::{component, view, IntoView}; diff --git a/src/faucet/views/mod.rs b/src/faucet/views/mod.rs index 56548255..89705409 100644 --- a/src/faucet/views/mod.rs +++ b/src/faucet/views/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + pub mod alert; pub mod balance; pub mod faucet; diff --git a/src/faucet/views/nav.rs b/src/faucet/views/nav.rs index 3ea18f5a..0832dd43 100644 --- a/src/faucet/views/nav.rs +++ b/src/faucet/views/nav.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use leptos::prelude::*; use leptos::{component, view, IntoView}; diff --git a/src/faucet/views/transaction.rs b/src/faucet/views/transaction.rs index edfa4230..38dce914 100644 --- a/src/faucet/views/transaction.rs +++ b/src/faucet/views/transaction.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use crate::utils::format::{format_url, SearchPath}; use ::cid::Cid; use leptos::prelude::*; diff --git a/src/lib.rs b/src/lib.rs index 520857dd..7c663cc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + mod app; mod utils; #[cfg(feature = "hydrate")] diff --git a/src/utils/address.rs b/src/utils/address.rs index 2ab8d38d..8e88faed 100644 --- a/src/utils/address.rs +++ b/src/utils/address.rs @@ -1,6 +1,9 @@ -use anyhow::ensure; +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use fvm_shared::address::{Address, Network}; use fvm_shared::ActorID; +use anyhow::ensure; // '0x' + 20bytes const ETH_ADDRESS_LENGTH: usize = 42; diff --git a/src/utils/error.rs b/src/utils/error.rs index a0b96f12..23ed8ba8 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use leptos::prelude::{RwSignal, Update}; use std::future::Future; use uuid::Uuid; diff --git a/src/utils/format.rs b/src/utils/format.rs index 3844633e..a51fa0c2 100644 --- a/src/utils/format.rs +++ b/src/utils/format.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use anyhow::{anyhow, Result}; use fvm_shared::econ::TokenAmount; use url::Url; diff --git a/src/utils/key.rs b/src/utils/key.rs index cb9747f2..cb0db1eb 100644 --- a/src/utils/key.rs +++ b/src/utils/key.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use anyhow::{Context as _, Result}; use bls_signatures::{PrivateKey as BlsPrivate, Serialize as _}; use libsecp256k1::{PublicKey as SecpPublic, SecretKey as SecpPrivate}; diff --git a/src/utils/lotus_json/address.rs b/src/utils/lotus_json/address.rs index 2d328df5..5738d352 100644 --- a/src/utils/lotus_json/address.rs +++ b/src/utils/lotus_json/address.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::*; diff --git a/src/utils/lotus_json/big_int.rs b/src/utils/lotus_json/big_int.rs index 78eba89c..61722b64 100644 --- a/src/utils/lotus_json/big_int.rs +++ b/src/utils/lotus_json/big_int.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::*; diff --git a/src/utils/lotus_json/cid.rs b/src/utils/lotus_json/cid.rs index 14127d08..b59a72b6 100644 --- a/src/utils/lotus_json/cid.rs +++ b/src/utils/lotus_json/cid.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::*; diff --git a/src/utils/lotus_json/message.rs b/src/utils/lotus_json/message.rs index 12a67702..46dabb93 100644 --- a/src/utils/lotus_json/message.rs +++ b/src/utils/lotus_json/message.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::*; diff --git a/src/utils/lotus_json/mod.rs b/src/utils/lotus_json/mod.rs index b2fe31c4..873eff74 100644 --- a/src/utils/lotus_json/mod.rs +++ b/src/utils/lotus_json/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT //! In the Filecoin ecosystem, there are TWO different ways to present a domain object: diff --git a/src/utils/lotus_json/opt.rs b/src/utils/lotus_json/opt.rs index b066e1d4..3e16f5fd 100644 --- a/src/utils/lotus_json/opt.rs +++ b/src/utils/lotus_json/opt.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::*; diff --git a/src/utils/lotus_json/raw_bytes.rs b/src/utils/lotus_json/raw_bytes.rs index 15452512..e9991180 100644 --- a/src/utils/lotus_json/raw_bytes.rs +++ b/src/utils/lotus_json/raw_bytes.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::{vec_u8::VecU8LotusJson, *}; diff --git a/src/utils/lotus_json/signature.rs b/src/utils/lotus_json/signature.rs index c7d6a8c1..a348b5ae 100644 --- a/src/utils/lotus_json/signature.rs +++ b/src/utils/lotus_json/signature.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::*; diff --git a/src/utils/lotus_json/signature_type.rs b/src/utils/lotus_json/signature_type.rs index 0b3a79b4..bbc15b88 100644 --- a/src/utils/lotus_json/signature_type.rs +++ b/src/utils/lotus_json/signature_type.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::*; diff --git a/src/utils/lotus_json/signed_message.rs b/src/utils/lotus_json/signed_message.rs index f5c4f3fe..dcfa6ef9 100644 --- a/src/utils/lotus_json/signed_message.rs +++ b/src/utils/lotus_json/signed_message.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::HasLotusJson; diff --git a/src/utils/lotus_json/token_amount.rs b/src/utils/lotus_json/token_amount.rs index c9a5d075..add0de07 100644 --- a/src/utils/lotus_json/token_amount.rs +++ b/src/utils/lotus_json/token_amount.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::*; diff --git a/src/utils/lotus_json/vec.rs b/src/utils/lotus_json/vec.rs index 8a802b0a..49f3aa47 100644 --- a/src/utils/lotus_json/vec.rs +++ b/src/utils/lotus_json/vec.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::*; diff --git a/src/utils/lotus_json/vec_u8.rs b/src/utils/lotus_json/vec_u8.rs index 84aa3ccd..58bbe2d5 100644 --- a/src/utils/lotus_json/vec_u8.rs +++ b/src/utils/lotus_json/vec_u8.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 ChainSafe Systems +// Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT use super::*; diff --git a/src/utils/message.rs b/src/utils/message.rs index 74452cce..9c2a040e 100644 --- a/src/utils/message.rs +++ b/src/utils/message.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use fvm_ipld_encoding::RawBytes; use fvm_shared::message::Message; use fvm_shared::{address::Address, econ::TokenAmount, METHOD_SEND}; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index ef28000c..8efe44d5 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + pub mod address; pub mod error; pub mod format; diff --git a/src/utils/rpc_context.rs b/src/utils/rpc_context.rs index 7ba72b9b..445378c6 100644 --- a/src/utils/rpc_context.rs +++ b/src/utils/rpc_context.rs @@ -1,3 +1,6 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + use cid::Cid; use fvm_shared::address::{set_current_network, Address, Network}; use fvm_shared::econ::TokenAmount; From df8f4258899d4d95ac8ef919526b22da59858f6d Mon Sep 17 00:00:00 2001 From: Shashank <shashank@chainsafe.io> Date: Tue, 27 May 2025 16:10:27 +0530 Subject: [PATCH 4/9] fmt --- src/faucet/mod.rs | 4 ++-- src/faucet/model.rs | 2 +- src/utils/address.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/faucet/mod.rs b/src/faucet/mod.rs index bd0caebb..4a4ab842 100644 --- a/src/faucet/mod.rs +++ b/src/faucet/mod.rs @@ -6,17 +6,17 @@ mod rate_limiter; pub mod calibnet; pub mod mainnet; +mod model; pub mod server; pub mod views; -mod model; use calibnet::{CALIBNET_DRIP_AMOUNT, CALIBNET_RATE_LIMIT_SECONDS, FIL_CALIBNET_UNIT}; use mainnet::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS}; +use crate::faucet::model::FaucetModel; use crate::utils::lotus_json::LotusJson; use crate::utils::rpc_context::Provider; use crate::utils::{address::parse_address, error::catch_all, message::message_transfer}; -use crate::faucet::model::FaucetModel; use cid::Cid; use fvm_shared::{address::Network, econ::TokenAmount}; use leptos::prelude::*; diff --git a/src/faucet/model.rs b/src/faucet/model.rs index dbb46657..c6c1e3d1 100644 --- a/src/faucet/model.rs +++ b/src/faucet/model.rs @@ -18,4 +18,4 @@ pub(super) struct FaucetModel { pub target_balance: LocalResource<TokenAmount>, pub sender_address: RwSignal<String>, pub target_address: RwSignal<String>, -} \ No newline at end of file +} diff --git a/src/utils/address.rs b/src/utils/address.rs index 8e88faed..1cf5ac45 100644 --- a/src/utils/address.rs +++ b/src/utils/address.rs @@ -1,9 +1,9 @@ // Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +use anyhow::ensure; use fvm_shared::address::{Address, Network}; use fvm_shared::ActorID; -use anyhow::ensure; // '0x' + 20bytes const ETH_ADDRESS_LENGTH: usize = 42; From 76177d6f0ef3ee7e8ce46b5608eea24bbe270dcf Mon Sep 17 00:00:00 2001 From: Shashank <shashank@chainsafe.io> Date: Wed, 28 May 2025 09:09:39 +0530 Subject: [PATCH 5/9] separate css from components --- src/app.rs | 2 +- src/faucet/calibnet/views.rs | 4 +- src/faucet/mainnet/views.rs | 4 +- src/faucet/views/alert.rs | 10 +- src/faucet/views/balance.rs | 15 +-- src/faucet/views/faucet.rs | 28 ++--- src/faucet/views/home.rs | 74 +++++------ src/faucet/views/icons.rs | 14 +-- src/faucet/views/layout.rs | 6 +- src/faucet/views/nav.rs | 4 +- src/faucet/views/transaction.rs | 14 +-- style/tailwind.css | 215 +++++++++++++++++++++++--------- 12 files changed, 229 insertions(+), 161 deletions(-) diff --git a/src/app.rs b/src/app.rs index 1ff280bb..b8462c0f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -19,7 +19,7 @@ pub fn App() -> impl IntoView { <Stylesheet href="/style.css" /> <Link rel="icon" type_="image/x-icon" href="/favicon.ico" /> <Router> - <div class="flex flex-col min-h-screen space-y-8 py-10 px-6 min-h-screen"> + <div class="app-container"> <Routes fallback=|| "Not found."> <Route path=path!("/") view=Explorer /> <Route path=path!("/faucet") view=Faucets /> diff --git a/src/faucet/calibnet/views.rs b/src/faucet/calibnet/views.rs index a2e88157..d0eebd7e 100644 --- a/src/faucet/calibnet/views.rs +++ b/src/faucet/calibnet/views.rs @@ -25,10 +25,10 @@ pub fn Faucet_Calibnet() -> impl IntoView { content="Filecoin Calibration Network Faucet dispensing tokens for testing purposes." /> <div> - <h1 class="text-4xl font-bold mb-6 text-center">Filecoin Calibnet Faucet</h1> + <h1 class="header">Filecoin Calibnet Faucet</h1> <Faucet target_network=Network::Testnet /> </div> - <div class="text-center mt-4"> + <div class="description"> "This faucet distributes " {format_balance(&CALIBNET_DRIP_AMOUNT, FIL_CALIBNET_UNIT)} " per request. It is rate-limited to 1 request per " {CALIBNET_RATE_LIMIT_SECONDS} " seconds. Farming is discouraged and will result in more stringent rate limiting in the future and/or permanent bans." diff --git a/src/faucet/mainnet/views.rs b/src/faucet/mainnet/views.rs index 4333e8f4..4bd01e4f 100644 --- a/src/faucet/mainnet/views.rs +++ b/src/faucet/mainnet/views.rs @@ -20,9 +20,9 @@ pub fn Faucet_Mainnet() -> impl IntoView { <Title text="Filecoin Faucet - Mainnet" /> <Meta name="description" content="Filecoin Mainnet Faucet dispensing tokens for testing purposes." /> <div> - <h1 class="text-4xl font-bold mb-6 text-center">Filecoin Mainnet Faucet</h1> + <h1 class="header">Filecoin Mainnet Faucet</h1> <Faucet target_network=Network::Mainnet /> - <div class="text-center mt-4"> + <div class="description"> "This faucet distributes " {format_balance(&MAINNET_DRIP_AMOUNT, FIL_MAINNET_UNIT)} " per request. It is rate-limited to 1 request per " {MAINNET_RATE_LIMIT_SECONDS} " seconds. Farming is discouraged and will result in more stringent rate limiting in the future and/or permanent bans or service termination. Faucet funds are limited and may run out. They are replenished periodically." diff --git a/src/faucet/views/alert.rs b/src/faucet/views/alert.rs index d6e9a757..357ce3f9 100644 --- a/src/faucet/views/alert.rs +++ b/src/faucet/views/alert.rs @@ -19,7 +19,7 @@ pub fn ErrorMessages( ) -> impl IntoView { let (fading_messages, set_fading_messages) = signal(HashSet::new()); view! { - <div class="fixed top-4 left-1/2 transform -translate-x-1/2 z-50"> + <div class="error-alert-container"> {errors .into_iter() .map(|(id, error)| { @@ -48,18 +48,14 @@ pub fn ErrorMessages( view! { <div class=move || { - if fading_messages.get().contains(&id) { - "opacity-0 transition-opacity bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-2 w-96" - } else { - "bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-2 w-96" - } + if fading_messages.get().contains(&id) { "error-alert--faded" } else { "error-alert" } } role="alert" > <span class="block sm:inline">{error}</span> <span class="absolute top-0 bottom-0 right-0 px-4 py-3"> <svg - class="fill-current h-6 w-6 text-red-500" + class="error-icon" role="button" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" diff --git a/src/faucet/views/balance.rs b/src/faucet/views/balance.rs index a54b034f..708d240e 100644 --- a/src/faucet/views/balance.rs +++ b/src/faucet/views/balance.rs @@ -11,7 +11,7 @@ use crate::utils::format::format_balance; pub fn FaucetBalance(faucet: RwSignal<FaucetController>) -> impl IntoView { view! { <div> - <h3 class="text-lg font-semibold">Faucet Balance:</h3> + <h3 class="title">Faucet Balance:</h3> <Transition fallback=move || { view! { <p>Loading faucet balance...</p> } }> @@ -19,19 +19,14 @@ pub fn FaucetBalance(faucet: RwSignal<FaucetController>) -> impl IntoView { if faucet.get().is_low_balance() { let topup_req_url = option_env!("FAUCET_TOPUP_REQ_URL"); view! { - <a - class="bg-orange-500 hover:bg-orange-600 text-white font-bold text-sm py-1 px-2 rounded" - target="_blank" - rel="noopener noreferrer" - href=topup_req_url - > + <a class="btn-topup" target="_blank" rel="noopener noreferrer" href=topup_req_url> "Request Faucet Top-up" </a> } .into_any() } else { view! { - <p class="text-xl"> + <p class="balance"> {format_balance(&faucet.get().get_faucet_balance(), &faucet.get().get_fil_unit())} </p> } @@ -47,9 +42,9 @@ pub fn FaucetBalance(faucet: RwSignal<FaucetController>) -> impl IntoView { pub fn TargetBalance(faucet: RwSignal<FaucetController>) -> impl IntoView { view! { <div> - <h3 class="text-lg font-semibold">Target Balance:</h3> + <h3 class="title">Target Balance:</h3> <Transition fallback=move || view! { <p>Loading target balance...</p> }> - <p class="text-xl"> + <p class="balance"> {move || format_balance(&faucet.get().get_target_balance(), &faucet.get().get_fil_unit())} </p> </Transition> diff --git a/src/faucet/views/faucet.rs b/src/faucet/views/faucet.rs index bf44af5b..f26a113f 100644 --- a/src/faucet/views/faucet.rs +++ b/src/faucet/views/faucet.rs @@ -18,7 +18,7 @@ use crate::faucet::FaucetController; #[component] fn FaucetInput(faucet: RwSignal<FaucetController>) -> impl IntoView { view! { - <div class="my-4 flex"> + <div class="input-container"> <input type="text" placeholder="Enter target address (Filecoin or Ethereum style)" @@ -31,12 +31,12 @@ fn FaucetInput(faucet: RwSignal<FaucetController>) -> impl IntoView { faucet.get().drip(); } } - class="flex-grow border border-gray-300 p-2 rounded-l" + class="input" /> {move || { if faucet.get().is_send_disabled() { view! { - <button class="bg-gray-400 text-white font-bold py-2 px-4 rounded-r" disabled=true> + <button class="btn-disabled" disabled=true> "Sending..." </button> } @@ -44,14 +44,14 @@ fn FaucetInput(faucet: RwSignal<FaucetController>) -> impl IntoView { } else if faucet.get().get_send_rate_limit_remaining() > 0 { let duration = faucet.get().get_send_rate_limit_remaining(); view! { - <button class="bg-gray-400 text-white font-bold py-2 px-4 rounded-r" disabled=true> + <button class="btn-disabled" disabled=true> {format!("Rate-limited! {duration}s")} </button> } .into_any() } else if faucet.get().is_low_balance() { view! { - <button class="bg-gray-400 text-white font-bold py-2 px-4 rounded-r" disabled=true> + <button class="btn-disabled" disabled=true> "Send" </button> } @@ -59,7 +59,7 @@ fn FaucetInput(faucet: RwSignal<FaucetController>) -> impl IntoView { } else { view! { <button - class="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded-r" + class="btn-enabled" on:click=move |_| { faucet.get().drip(); } @@ -121,13 +121,13 @@ pub fn Faucet(target_network: Network) -> impl IntoView { ().into_any() } }} - <div class="max-w-2xl mx-auto"> + <div class="faucet-container"> <FaucetInput faucet=faucet /> - <div class="flex justify-between my-4"> + <div class="balance-container"> <FaucetBalance faucet=faucet /> <TargetBalance faucet=faucet /> </div> - <hr class="my-4 border-t border-gray-300" /> + <hr class="separator" /> {move || { let messages = faucet.get().get_sent_messages(); if !messages.is_empty() { @@ -137,7 +137,7 @@ pub fn Faucet(target_network: Network) -> impl IntoView { } }} </div> - <div class="flex justify-center space-x-4"> + <div class="nav-container"> <TransactionHistoryButton faucet=faucet faucet_tx_base_url=faucet_tx_base_url /> <GotoFaucetList /> </div> @@ -149,13 +149,13 @@ pub fn Faucets() -> impl IntoView { view! { <Title text="Filecoin Faucets" /> <Meta name="description" content="Filecoin Faucet list" /> - <div class="text-center space-y-8"> - <h1 class="text-4xl font-bold mb-6 text-center">Filecoin Faucet List</h1> - <a class="text-blue-600" href="/faucet/calibnet"> + <div class="faucet-list-container"> + <h1 class="header">Filecoin Faucet List</h1> + <a class="link-text-hover" href="/faucet/calibnet"> Calibration Network Faucet </a> <br /> - <a class="text-blue-600" href="/faucet/mainnet"> + <a class="link-text-hover" href="/faucet/mainnet"> Mainnet Network Faucet </a> <GotoHome /> diff --git a/src/faucet/views/home.rs b/src/faucet/views/home.rs index bc756316..5062da92 100644 --- a/src/faucet/views/home.rs +++ b/src/faucet/views/home.rs @@ -12,56 +12,53 @@ use leptos::{component, leptos_dom::helpers::event_target_value, view, IntoView} #[component] fn FaucetOverview() -> impl IntoView { view! { - <div class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl w-full m-auto"> - <div class="bg-white p-6 rounded-lg border border-gray-100"> - <h2 class="text-lg font-semibold text-gray-900 mb-4">What does the faucet offer?</h2> - <ul class="space-y-3"> - <li class="flex items-start"> + <div class="overview-container"> + <div class="card"> + <h2 class="card-title">What does the faucet offer?</h2> + <ul class="list"> + <li class="list-element"> <CheckIcon /> - <span class="text-gray-600">Free calibnet tFIL for experimentation and development.</span> + <span class="list-text">Free calibnet tFIL for experimentation and development.</span> </li> - <li class="flex items-start"> + <li class="list-element"> <CheckIcon /> - <span class="text-gray-600"> + <span class="list-text"> Real mainnet FIL for contributors engaging with the Filecoin ecosystem. </span> </li> - <li class="flex items-start"> + <li class="list-element"> <CheckIcon /> - <span class="text-gray-600"> + <span class="list-text"> A Quick and Easy way to request free tFIL and FIL - Just enter your wallet address. </span> </li> </ul> </div> - <div class="bg-white p-6 rounded-lg border border-gray-100"> - <h2 class="text-lg font-semibold text-gray-900 mb-4">Why use this faucet?</h2> - <ul class="space-y-3"> - <li class="flex items-start"> - <LightningIcon class="text-blue-500".to_string() /> - <span class="text-gray-600">Supports both calibnet and mainnet, unlike typical faucets.</span> + <div class="card"> + <h2 class="card-title">Why use this faucet?</h2> + <ul class="list"> + <li class="list-element"> + <LightningIcon /> + <span class="list-text">Supports both calibnet and mainnet, unlike typical faucets.</span> </li> - <li class="flex items-start"> - <LightningIcon class="text-blue-500".to_string() /> - <span class="text-gray-600"> + <li class="list-element"> + <LightningIcon /> + <span class="list-text"> Enables testing of smart contracts, storage deals, and blockchain interactions without financial risk. </span> </li> - <li class="flex items-start"> - <LightningIcon class="text-blue-500".to_string() /> - <span class="text-gray-600"> - Easy access to FIL for developers and users building on Filecoin. - </span> + <li class="list-element"> + <LightningIcon /> + <span class="list-text">Easy access to FIL for developers and users building on Filecoin.</span> </li> - <li class="flex items-start"> - <LightningIcon class="text-blue-500".to_string() /> - <span class="text-gray-600"> - Need help? Visit the - <a class="text-blue-500" href="https://filecoin.io/slack" target="_blank"> + <li class="list-element"> + <LightningIcon /> + <span class="list-text"> + Need help? Visit the <a class="link-text" href="https://filecoin.io/slack" target="_blank"> {" "} Filecoin Slack - </a>{" "}or <a class="text-blue-500" href="https://docs.filecoin.io" target="_blank"> + </a>{" "}or <a class="link-text" href="https://docs.filecoin.io" target="_blank"> {" "} documentation </a>. @@ -80,23 +77,20 @@ fn NetworkSelection( network_version: LocalResource<Option<u64>>, ) -> impl IntoView { view! { - <div class="space-y-6 flex flex-col items-center"> - <div class="relative w-64"> - <select - on:change=move |ev| { rpc_context.set(event_target_value(&ev)) } - class="w-full px-4 py-2 text-sm text-gray-700 bg-white border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 hover:border-gray-400 transition-colors cursor-pointer appearance-none" - > + <div class="network-selector"> + <div class="dropdown"> + <select on:change=move |ev| { rpc_context.set(event_target_value(&ev)) } class="dropdown-items"> <option value=Provider::get_network_url(Network::Testnet)>Glif.io Calibnet</option> <option value=Provider::get_network_url(Network::Mainnet)>Glif.io Mainnet</option> </select> - <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"> + <div class="dropdown-icon"> <svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /> </svg> </div> </div> - <div class="flex items-center w-[300px] justify-between"> + <div class="network-info"> <p>Network:</p> <Transition fallback=move || view! { <p>Loading network name...</p> }> <p> @@ -106,7 +100,7 @@ fn NetworkSelection( </Transition> </div> - <div class="flex items-center w-[300px] justify-between"> + <div class="network-info"> <p>Version:</p> <Transition fallback=move || view! { <p>Loading network version...</p> }> <p> @@ -133,7 +127,7 @@ pub fn Explorer() -> impl IntoView { }); view! { - <main class="min-h-screen flex flex-col flex-grow space-y-8"> + <main class="main-container"> <Header /> <FaucetOverview /> <NetworkSelection rpc_context=rpc_context network_name=network_name network_version=network_version /> diff --git a/src/faucet/views/icons.rs b/src/faucet/views/icons.rs index 7aaed8f4..53f6def3 100644 --- a/src/faucet/views/icons.rs +++ b/src/faucet/views/icons.rs @@ -6,12 +6,7 @@ use leptos::prelude::*; #[component] pub fn CheckIcon(#[prop(default = String::new())] class: String) -> impl IntoView { view! { - <svg - class=format!("h-5 w-5 text-green-500 mr-2 flex-shrink-0 {}", class) - fill="none" - stroke="currentColor" - viewBox="0 0 24 24" - > + <svg class=format!("check-icon {}", class) fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" /> </svg> } @@ -20,12 +15,7 @@ pub fn CheckIcon(#[prop(default = String::new())] class: String) -> impl IntoVie #[component] pub fn LightningIcon(#[prop(default = String::new())] class: String) -> impl IntoView { view! { - <svg - class=format!("h-5 w-5 text-blue-500 mr-2 flex-shrink-0 {}", class) - fill="none" - stroke="currentColor" - viewBox="0 0 24 24" - > + <svg class=format!("lighting-icon {}", class) fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /> </svg> } diff --git a/src/faucet/views/layout.rs b/src/faucet/views/layout.rs index fcde9afd..ea8e66be 100644 --- a/src/faucet/views/layout.rs +++ b/src/faucet/views/layout.rs @@ -8,9 +8,7 @@ use leptos::{component, view, IntoView}; pub fn Header() -> impl IntoView { view! { <header class="space-y-6 flex flex-col items-center"> - <h1 class="text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl"> - Filecoin Forest Explorer Faucet - </h1> + <h1 class="header-large">Filecoin Forest Explorer Faucet</h1> <p class="max-w-2xl text-center"> The Filecoin Forest Explorer Faucet provides developers and users with free calibnet(tFil) and mainnet(FIL) to support their exploration, testing and development on the Filecoin network. </p> @@ -21,7 +19,7 @@ pub fn Header() -> impl IntoView { #[component] pub fn Footer() -> impl IntoView { view! { - <footer class="py-4 text-center"> + <footer class="footer"> <a class="text-green-600" target="_blank" diff --git a/src/faucet/views/nav.rs b/src/faucet/views/nav.rs index 0832dd43..9a708476 100644 --- a/src/faucet/views/nav.rs +++ b/src/faucet/views/nav.rs @@ -8,7 +8,7 @@ use leptos::{component, view, IntoView}; pub fn GotoFaucetList() -> impl IntoView { view! { <div class="text-center"> - <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg"> + <button class="btn"> <a href="/faucet">Faucet List</a> </button> </div> @@ -19,7 +19,7 @@ pub fn GotoFaucetList() -> impl IntoView { pub fn GotoHome() -> impl IntoView { view! { <div class="text-center"> - <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg"> + <button class="btn"> <a href="/">Home</a> </button> </div> diff --git a/src/faucet/views/transaction.rs b/src/faucet/views/transaction.rs index 38dce914..2fd3e094 100644 --- a/src/faucet/views/transaction.rs +++ b/src/faucet/views/transaction.rs @@ -15,9 +15,9 @@ pub fn TransactionList( faucet_tx_base_url: RwSignal<Option<Url>>, ) -> impl IntoView { view! { - <div class="mt-4 space-y-2"> - <h3 class="text-lg font-semibold">Transactions:</h3> - <ul class="list-disc pl-5"> + <div class="transaction-container"> + <h3 class="title">Transactions:</h3> + <ul class="bullet-list"> {messages .into_iter() .map(|(msg, sent)| { @@ -30,11 +30,7 @@ pub fn TransactionList( }) .map(|tx_url| { view! { - <a - href=tx_url.to_string() - target="_blank" - class="text-blue-600 hover:underline" - > + <a href=tx_url.to_string() target="_blank" class="link-text-hover"> {msg.to_string()} </a> } @@ -66,7 +62,7 @@ pub fn TransactionHistoryButton( match format_url(base_url, SearchPath::Address, &faucet.get().get_sender_address()) { Ok(addr_url) => { view! { - <button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg"> + <button class="btn"> <a href=addr_url.to_string() target="_blank" rel="noopener noreferrer"> "Transaction History" </a> diff --git a/style/tailwind.css b/style/tailwind.css index 53873041..4afeca5b 100644 --- a/style/tailwind.css +++ b/style/tailwind.css @@ -2,76 +2,175 @@ @tailwind components; @tailwind utilities; -/* HTML: <span class="loader"></span> */ -.loader { - display: inline-block; - width: 1em; - aspect-ratio: 1; - border-radius: 50%; - border: 3px solid #514b82; - animation: - l20-1 0.8s infinite linear alternate, - l20-2 1.6s infinite linear; - margin-left: 0.5em; - margin-right: 0.5em; -} - -@keyframes l20-1 { - 0% { - clip-path: polygon(50% 50%, 0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0%) +@layer components { + .app-container { + @apply flex flex-col min-h-screen space-y-8 py-10 px-6 min-h-screen; } - - 12.5% { - clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 0%, 100% 0%, 100% 0%) + .main-container { + @apply min-h-screen flex flex-col flex-grow space-y-8; } - - 25% { - clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 100% 100%, 100% 100%) + .overview-container { + @apply grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl w-full m-auto; } - - 50% { - clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%) + .faucet-list-container { + @apply text-center space-y-8; } - - 62.5% { - clip-path: polygon(50% 50%, 100% 0, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%) + .faucet-container { + @apply max-w-2xl mx-auto; } - - 75% { - clip-path: polygon(50% 50%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0% 100%) + .input-container { + @apply my-4 flex; } - - 100% { - clip-path: polygon(50% 50%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0% 100%) + .balance-container { + @apply flex justify-between my-4; } -} - -@keyframes l20-2 { - 0% { - transform: scaleY(1) rotate(0deg) + .transaction-container { + @apply mt-4 space-y-2; } - - 49.99% { - transform: scaleY(1) rotate(135deg) + .nav-container { + @apply flex justify-center space-x-4; } - - 50% { - transform: scaleY(-1) rotate(0deg) + .error-alert-container { + @apply fixed top-4 left-1/2 transform -translate-x-1/2 z-50; } - - 100% { - transform: scaleY(-1) rotate(-135deg) + .network-info { + @apply flex items-center w-[300px] justify-between; + } + .network-selector { + @apply space-y-6 flex flex-col items-center; + } + .separator { + @apply my-4 border-t border-gray-300; + } + .card { + @apply bg-white p-6 rounded-lg border border-gray-100; + } + .card-title { + @apply text-lg font-semibold text-gray-900 mb-4; + } + .input { + @apply flex-grow border border-gray-300 p-2 rounded-l; + } + .title { + @apply text-lg font-semibold; + } + .list { + @apply space-y-3; + } + .bullet-list { + @apply list-disc pl-5; + } + .list-element { + @apply flex items-start; + } + .list-text { + @apply text-gray-600; + } + .link-text { + @apply text-blue-500; + } + .link-text-hover { + @apply text-blue-500 hover:text-blue-600; + } + .description { + @apply text-center mt-4; + } + .balance { + @apply text-xl; + } + .header { + @apply text-4xl font-bold mb-6 text-center; + } + .header-large { + @apply text-4xl font-extrabold leading-none tracking-tight text-gray-900 md:text-5xl lg:text-6xl; + } + .footer { + @apply py-4 text-center; + } + .btn { + @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg; + } + .btn-enabled { + @apply bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded-r; + } + .btn-disabled { + @apply bg-gray-400 text-white font-bold py-2 px-4 rounded-r; + } + .btn-topup { + @apply bg-orange-500 hover:bg-orange-600 text-white font-bold text-sm py-1 px-2 rounded; + } + .dropdown { + @apply relative w-64; + } + .dropdown-icon { + @apply pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700; + } + .dropdown-items { + @apply w-full px-4 py-2 text-sm text-gray-700 bg-white border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 hover:border-gray-400 transition-colors cursor-pointer appearance-none; + } + .check-icon { + @apply h-5 w-5 text-green-500 mr-2 flex-shrink-0; + } + .lighting-icon { + @apply h-5 w-5 text-blue-500 mr-2 flex-shrink-0; + } + .error-icon { + @apply fill-current h-6 w-6 text-red-500; + } + .error-alert { + @apply bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-2 w-96; + } + .error-alert--faded { + @apply opacity-0 transition-opacity bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-2 w-96; + } + @keyframes l20-1 { + 0% { + clip-path: polygon(50% 50%, 0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0%); + } + 12.5% { + clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 0%, 100% 0%, 100% 0%); + } + 25% { + clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 100% 100%, 100% 100%); + } + 50% { + clip-path: polygon(50% 50%, 0 0, 50% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%); + } + 62.5% { + clip-path: polygon(50% 50%, 100% 0, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100%); + } + 75% { + clip-path: polygon(50% 50%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0% 100%); + } + 100% { + clip-path: polygon(50% 50%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0% 100%); + } } -} -.opacity-0 { - opacity: 0; + @keyframes l20-2 { + 0% { + transform: scaleY(1) rotate(0deg); + } + 49.99% { + transform: scaleY(1) rotate(135deg); + } + 50% { + transform: scaleY(-1) rotate(0deg); + } + 100% { + transform: scaleY(-1) rotate(-135deg); + } + } } -.opacity-100 { - opacity: 1; +@layer utilities { + .opacity-0 { + opacity: 0; + } + .opacity-100 { + opacity: 1; + } + .transition-opacity { + transition: opacity 0.5s ease-in-out; + } } - -.transition-opacity { - transition: opacity 0.5s ease-in-out; -} \ No newline at end of file From bc87ac63ffabdf2e38ccd04e800b75909cf30ebc Mon Sep 17 00:00:00 2001 From: Shashank <shashank@chainsafe.io> Date: Wed, 28 May 2025 11:24:29 +0530 Subject: [PATCH 6/9] Add components mermaid diagram --- docs/components.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs/components.md diff --git a/docs/components.md b/docs/components.md new file mode 100644 index 00000000..be6afb10 --- /dev/null +++ b/docs/components.md @@ -0,0 +1,32 @@ +Forest Explorer Components Diagram + +```mermaid +graph TD + subgraph src["Forest Explorer"] + subgraph utils["Utils"] + message["Message transfer<br/>message.rs"] + format["Formatting balance & URL<br/>format.rs"] + key["KeyPair<br/>key.rs"] + error["Error handler<br/>error.rs"] + address["Address utils<br/>address.rs"] + rpc_context["Supported RPC's<br/>rpc_context.rs"] + lotus_json["Lotus JSON<br/>lotus_json/"] + end + subgraph faucet["Faucet"] + model["Faucet Model<br/>model.rs"] + rate_limiter["Rate Limiter<br/>rate_limiter.rs"] + calibnet["Calibnet Faucet Specific<br/>/faucet/calibnet/"] + mainnet["Mainnet Faucet Specific<br/>/faucet/mainnet/"] + subgraph views["UI Components"] + home["Home page view<br/>home.rs"] + layout["Page layouts view<br/>layout.rs"] + faucet_view["Faucet page view<br/>faucet.rs"] + transaction["Transactions view<br/>transaction.rs"] + balance["Balance view<br/>balance.rs"] + alert["Error view<br/>alert.rs"] + nav["Navigation view<br/>nav.rs"] + icons["Icons<br/>icons.rs"] + end + end + end +``` From dbb5acb4902e9f7fcade9f72133f9b1bc89bacf2 Mon Sep 17 00:00:00 2001 From: Shashank <shashank@chainsafe.io> Date: Wed, 28 May 2025 12:50:09 +0530 Subject: [PATCH 7/9] split controller --- docs/components.md | 1 + src/faucet/controller.rs | 246 +++++++++++++++++++++++++++++++ src/faucet/mod.rs | 247 +------------------------------- src/faucet/views/alert.rs | 2 +- src/faucet/views/balance.rs | 2 +- src/faucet/views/faucet.rs | 2 +- src/faucet/views/transaction.rs | 2 +- 7 files changed, 253 insertions(+), 249 deletions(-) create mode 100644 src/faucet/controller.rs diff --git a/docs/components.md b/docs/components.md index be6afb10..824e9e41 100644 --- a/docs/components.md +++ b/docs/components.md @@ -14,6 +14,7 @@ graph TD end subgraph faucet["Faucet"] model["Faucet Model<br/>model.rs"] + model["Faucet Controller<br/>controller.rs"] rate_limiter["Rate Limiter<br/>rate_limiter.rs"] calibnet["Calibnet Faucet Specific<br/>/faucet/calibnet/"] mainnet["Mainnet Faucet Specific<br/>/faucet/mainnet/"] diff --git a/src/faucet/controller.rs b/src/faucet/controller.rs new file mode 100644 index 00000000..8f67e478 --- /dev/null +++ b/src/faucet/controller.rs @@ -0,0 +1,246 @@ +// Copyright 2019-2025 ChainSafe Systems +// SPDX-License-Identifier: Apache-2.0, MIT + +use super::calibnet::{CALIBNET_DRIP_AMOUNT, CALIBNET_RATE_LIMIT_SECONDS, FIL_CALIBNET_UNIT}; +use super::mainnet::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS}; + +use super::server::{faucet_address, sign_with_secret_key}; +use crate::faucet::model::FaucetModel; +use crate::utils::lotus_json::LotusJson; +use crate::utils::rpc_context::Provider; +use crate::utils::{address::parse_address, error::catch_all, message::message_transfer}; +use cid::Cid; +use fvm_shared::{address::Network, econ::TokenAmount}; +use leptos::prelude::*; +use leptos::task::spawn_local; +use uuid::Uuid; + +#[derive(Clone)] +pub struct FaucetController { + faucet: FaucetModel, +} + +impl FaucetController { + pub fn new(network: Network) -> Self { + let is_mainnet = network == Network::Mainnet; + let balance_trigger = Trigger::new(); + let sender_address = RwSignal::new(String::new()); + let target_address = RwSignal::new(String::new()); + let target_balance = LocalResource::new(move || { + let target_address = target_address.get(); + balance_trigger.track(); + async move { + if let Ok(address) = parse_address(&target_address, network) { + Provider::from_network(network) + .wallet_balance(address) + .await + .ok() + .unwrap_or(TokenAmount::from_atto(0)) + } else { + TokenAmount::from_atto(0) + } + } + }); + let faucet_address = LocalResource::new(move || async move { + faucet_address(is_mainnet) + .await + .map(|LotusJson(addr)| addr) + .ok() + }); + let faucet_balance = LocalResource::new(move || { + balance_trigger.track(); + async move { + if let Some(addr) = faucet_address.await { + sender_address.set(addr.to_string()); + Provider::from_network(network) + .wallet_balance(addr) + .await + .ok() + .unwrap_or(TokenAmount::from_atto(0)) + } else { + TokenAmount::from_atto(0) + } + } + }); + let faucet = FaucetModel { + network, + send_disabled: RwSignal::new(false), + send_limited: RwSignal::new(0), + sent_messages: RwSignal::new(Vec::new()), + error_messages: RwSignal::new(Vec::new()), + balance_trigger, + target_balance, + faucet_balance, + sender_address, + target_address, + }; + Self { faucet } + } + + #[allow(dead_code)] + pub fn refetch_balances(&self) { + use leptos::prelude::GetUntracked; + + log::info!("Checking for new transactions"); + self.faucet.balance_trigger.notify(); + let pending = self + .faucet + .sent_messages + .get_untracked() + .into_iter() + .filter_map(|(cid, sent)| if !sent { Some(cid) } else { None }) + .collect::<Vec<_>>(); + + let network = self.faucet.network; + let messages = self.faucet.sent_messages; + spawn_local(catch_all(self.faucet.error_messages, async move { + for cid in pending { + if let Some(lookup) = Provider::from_network(network) + .state_search_msg(cid) + .await? + { + messages.update(|messages| { + for (cid, sent) in messages { + if cid == &lookup.message { + *sent = true; + } + } + }); + } + } + Ok(()) + })); + } + pub fn get_target_balance(&self) -> TokenAmount { + self.faucet.target_balance.get().unwrap_or_default() + } + + pub fn get_sender_address(&self) -> String { + self.faucet.sender_address.get() + } + + pub fn get_target_address(&self) -> String { + self.faucet.target_address.get() + } + + pub fn get_fil_unit(&self) -> String { + match self.faucet.network { + Network::Mainnet => FIL_MAINNET_UNIT, + _ => FIL_CALIBNET_UNIT, + } + .to_string() + } + + pub fn set_target_address(&self, address: String) { + self.faucet.target_address.set(address); + } + + pub fn get_faucet_balance(&self) -> TokenAmount { + self.faucet.faucet_balance.get().unwrap_or_default() + } + + pub fn get_error_messages(&self) -> Vec<(Uuid, String)> { + self.faucet.error_messages.get().clone() + } + + pub fn add_error_message(&self, message: String) { + self.faucet.error_messages.update(|messages| { + messages.push((Uuid::new_v4(), message)); + }); + } + + pub fn remove_error_message(&self, id: Uuid) { + self.faucet.error_messages.update(|messages| { + messages.retain(|(x, _)| *x != id); + }); + } + + pub fn get_sent_messages(&self) -> Vec<(Cid, bool)> { + self.faucet.sent_messages.get().clone() + } + + pub fn is_send_disabled(&self) -> bool { + self.faucet.send_disabled.get() + } + + pub fn get_send_rate_limit_remaining(&self) -> i32 { + self.faucet.send_limited.get() + } + + #[allow(dead_code)] + pub fn set_send_rate_limit_remaining(&self, remaining: i32) { + self.faucet.send_limited.set(remaining); + } + + fn get_drip_amount(&self) -> TokenAmount { + if self.faucet.network == Network::Mainnet { + MAINNET_DRIP_AMOUNT.clone() + } else { + CALIBNET_DRIP_AMOUNT.clone() + } + } + + pub fn is_low_balance(&self) -> bool { + let target_balance = self.get_faucet_balance(); + let drip_amount = self.get_drip_amount(); + target_balance < drip_amount + } + + pub fn drip(&self) { + let is_mainnet = self.faucet.network == Network::Mainnet; + let faucet = self.faucet.clone(); + match parse_address(&self.faucet.target_address.get(), self.faucet.network) { + Ok(addr) => { + spawn_local(async move { + catch_all(faucet.error_messages, async move { + let rpc = Provider::from_network(faucet.network); + let LotusJson(from) = faucet_address(is_mainnet) + .await + .map_err(|e| anyhow::anyhow!("Error getting faucet address: {}", e))?; + faucet.send_disabled.set(true); + let nonce = rpc.mpool_get_nonce(from).await?; + let mut msg = message_transfer( + from, + addr, + if is_mainnet { + MAINNET_DRIP_AMOUNT.clone() + } else { + CALIBNET_DRIP_AMOUNT.clone() + }, + ); + msg.sequence = nonce; + let msg = rpc.estimate_gas(msg).await?; + match sign_with_secret_key(LotusJson(msg.clone()), is_mainnet).await { + Ok(LotusJson(smsg)) => { + let cid = rpc.mpool_push(smsg).await?; + faucet.sent_messages.update(|messages| { + messages.push((cid, false)); + }); + log::info!("Sent message: {:?}", cid); + } + Err(e) => { + log::error!("Failed to sign message: {}", e); + let rate_limit_seconds = if is_mainnet { + MAINNET_RATE_LIMIT_SECONDS + } else { + CALIBNET_RATE_LIMIT_SECONDS + }; + faucet.send_limited.set(rate_limit_seconds as i32); + } + } + Ok(()) + }) + .await; + faucet.send_disabled.set(false); + }); + } + Err(e) => { + self.add_error_message(format!( + "Invalid address: {}", + &self.faucet.target_address.get() + )); + log::error!("Error parsing address: {}", e); + } + } + } +} diff --git a/src/faucet/mod.rs b/src/faucet/mod.rs index 4a4ab842..7971118e 100644 --- a/src/faucet/mod.rs +++ b/src/faucet/mod.rs @@ -1,255 +1,12 @@ // Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +mod controller; +mod model; #[cfg(feature = "ssr")] mod rate_limiter; pub mod calibnet; pub mod mainnet; -mod model; pub mod server; pub mod views; - -use calibnet::{CALIBNET_DRIP_AMOUNT, CALIBNET_RATE_LIMIT_SECONDS, FIL_CALIBNET_UNIT}; -use mainnet::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS}; - -use crate::faucet::model::FaucetModel; -use crate::utils::lotus_json::LotusJson; -use crate::utils::rpc_context::Provider; -use crate::utils::{address::parse_address, error::catch_all, message::message_transfer}; -use cid::Cid; -use fvm_shared::{address::Network, econ::TokenAmount}; -use leptos::prelude::*; -use leptos::task::spawn_local; -use server::{faucet_address, sign_with_secret_key}; -use uuid::Uuid; - -#[derive(Clone)] -pub(super) struct FaucetController { - faucet: FaucetModel, -} - -impl FaucetController { - pub fn new(network: Network) -> Self { - let is_mainnet = network == Network::Mainnet; - let balance_trigger = Trigger::new(); - let sender_address = RwSignal::new(String::new()); - let target_address = RwSignal::new(String::new()); - let target_balance = LocalResource::new(move || { - let target_address = target_address.get(); - balance_trigger.track(); - async move { - if let Ok(address) = parse_address(&target_address, network) { - Provider::from_network(network) - .wallet_balance(address) - .await - .ok() - .unwrap_or(TokenAmount::from_atto(0)) - } else { - TokenAmount::from_atto(0) - } - } - }); - let faucet_address = LocalResource::new(move || async move { - faucet_address(is_mainnet) - .await - .map(|LotusJson(addr)| addr) - .ok() - }); - let faucet_balance = LocalResource::new(move || { - balance_trigger.track(); - async move { - if let Some(addr) = faucet_address.await { - sender_address.set(addr.to_string()); - Provider::from_network(network) - .wallet_balance(addr) - .await - .ok() - .unwrap_or(TokenAmount::from_atto(0)) - } else { - TokenAmount::from_atto(0) - } - } - }); - let faucet = FaucetModel { - network, - send_disabled: RwSignal::new(false), - send_limited: RwSignal::new(0), - sent_messages: RwSignal::new(Vec::new()), - error_messages: RwSignal::new(Vec::new()), - balance_trigger, - target_balance, - faucet_balance, - sender_address, - target_address, - }; - Self { faucet } - } - - #[allow(dead_code)] - pub fn refetch_balances(&self) { - use leptos::prelude::GetUntracked; - - log::info!("Checking for new transactions"); - self.faucet.balance_trigger.notify(); - let pending = self - .faucet - .sent_messages - .get_untracked() - .into_iter() - .filter_map(|(cid, sent)| if !sent { Some(cid) } else { None }) - .collect::<Vec<_>>(); - - let network = self.faucet.network; - let messages = self.faucet.sent_messages; - spawn_local(catch_all(self.faucet.error_messages, async move { - for cid in pending { - if let Some(lookup) = Provider::from_network(network) - .state_search_msg(cid) - .await? - { - messages.update(|messages| { - for (cid, sent) in messages { - if cid == &lookup.message { - *sent = true; - } - } - }); - } - } - Ok(()) - })); - } - pub fn get_target_balance(&self) -> TokenAmount { - self.faucet.target_balance.get().unwrap_or_default() - } - - pub fn get_sender_address(&self) -> String { - self.faucet.sender_address.get() - } - - pub fn get_target_address(&self) -> String { - self.faucet.target_address.get() - } - - pub fn get_fil_unit(&self) -> String { - match self.faucet.network { - Network::Mainnet => FIL_MAINNET_UNIT, - _ => FIL_CALIBNET_UNIT, - } - .to_string() - } - - pub fn set_target_address(&self, address: String) { - self.faucet.target_address.set(address); - } - - pub fn get_faucet_balance(&self) -> TokenAmount { - self.faucet.faucet_balance.get().unwrap_or_default() - } - - pub fn get_error_messages(&self) -> Vec<(Uuid, String)> { - self.faucet.error_messages.get().clone() - } - - pub fn add_error_message(&self, message: String) { - self.faucet.error_messages.update(|messages| { - messages.push((Uuid::new_v4(), message)); - }); - } - - pub fn remove_error_message(&self, id: Uuid) { - self.faucet.error_messages.update(|messages| { - messages.retain(|(x, _)| *x != id); - }); - } - - pub fn get_sent_messages(&self) -> Vec<(Cid, bool)> { - self.faucet.sent_messages.get().clone() - } - - pub fn is_send_disabled(&self) -> bool { - self.faucet.send_disabled.get() - } - - pub fn get_send_rate_limit_remaining(&self) -> i32 { - self.faucet.send_limited.get() - } - - #[allow(dead_code)] - pub fn set_send_rate_limit_remaining(&self, remaining: i32) { - self.faucet.send_limited.set(remaining); - } - - fn get_drip_amount(&self) -> TokenAmount { - if self.faucet.network == Network::Mainnet { - MAINNET_DRIP_AMOUNT.clone() - } else { - CALIBNET_DRIP_AMOUNT.clone() - } - } - - pub fn is_low_balance(&self) -> bool { - let target_balance = self.get_faucet_balance(); - let drip_amount = self.get_drip_amount(); - target_balance < drip_amount - } - - pub fn drip(&self) { - let is_mainnet = self.faucet.network == Network::Mainnet; - let faucet = self.faucet.clone(); - match parse_address(&self.faucet.target_address.get(), self.faucet.network) { - Ok(addr) => { - spawn_local(async move { - catch_all(faucet.error_messages, async move { - let rpc = Provider::from_network(faucet.network); - let LotusJson(from) = faucet_address(is_mainnet) - .await - .map_err(|e| anyhow::anyhow!("Error getting faucet address: {}", e))?; - faucet.send_disabled.set(true); - let nonce = rpc.mpool_get_nonce(from).await?; - let mut msg = message_transfer( - from, - addr, - if is_mainnet { - MAINNET_DRIP_AMOUNT.clone() - } else { - CALIBNET_DRIP_AMOUNT.clone() - }, - ); - msg.sequence = nonce; - let msg = rpc.estimate_gas(msg).await?; - match sign_with_secret_key(LotusJson(msg.clone()), is_mainnet).await { - Ok(LotusJson(smsg)) => { - let cid = rpc.mpool_push(smsg).await?; - faucet.sent_messages.update(|messages| { - messages.push((cid, false)); - }); - log::info!("Sent message: {:?}", cid); - } - Err(e) => { - log::error!("Failed to sign message: {}", e); - let rate_limit_seconds = if is_mainnet { - MAINNET_RATE_LIMIT_SECONDS - } else { - CALIBNET_RATE_LIMIT_SECONDS - }; - faucet.send_limited.set(rate_limit_seconds as i32); - } - } - Ok(()) - }) - .await; - faucet.send_disabled.set(false); - }); - } - Err(e) => { - self.add_error_message(format!( - "Invalid address: {}", - &self.faucet.target_address.get() - )); - log::error!("Error parsing address: {}", e); - } - } - } -} diff --git a/src/faucet/views/alert.rs b/src/faucet/views/alert.rs index 357ce3f9..50a46fb3 100644 --- a/src/faucet/views/alert.rs +++ b/src/faucet/views/alert.rs @@ -1,7 +1,7 @@ // Copyright 2019-2025 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use crate::faucet::FaucetController; +use crate::faucet::controller::FaucetController; use leptos::prelude::*; use leptos::task::spawn_local; use leptos::{component, view, IntoView}; diff --git a/src/faucet/views/balance.rs b/src/faucet/views/balance.rs index 708d240e..af1ed05f 100644 --- a/src/faucet/views/balance.rs +++ b/src/faucet/views/balance.rs @@ -4,7 +4,7 @@ use leptos::prelude::*; use leptos::{component, view, IntoView}; -use crate::faucet::FaucetController; +use crate::faucet::controller::FaucetController; use crate::utils::format::format_balance; #[component] diff --git a/src/faucet/views/faucet.rs b/src/faucet/views/faucet.rs index f26a113f..58159ae1 100644 --- a/src/faucet/views/faucet.rs +++ b/src/faucet/views/faucet.rs @@ -9,11 +9,11 @@ use leptos_meta::{Meta, Title}; use leptos_use::use_interval_fn; use url::Url; +use crate::faucet::controller::FaucetController; use crate::faucet::views::alert::ErrorMessages; use crate::faucet::views::balance::{FaucetBalance, TargetBalance}; use crate::faucet::views::nav::{GotoFaucetList, GotoHome}; use crate::faucet::views::transaction::{TransactionHistoryButton, TransactionList}; -use crate::faucet::FaucetController; #[component] fn FaucetInput(faucet: RwSignal<FaucetController>) -> impl IntoView { diff --git a/src/faucet/views/transaction.rs b/src/faucet/views/transaction.rs index 2fd3e094..36dcdbf2 100644 --- a/src/faucet/views/transaction.rs +++ b/src/faucet/views/transaction.rs @@ -7,7 +7,7 @@ use leptos::prelude::*; use leptos::{component, view, IntoView}; use url::Url; -use crate::faucet::FaucetController; +use crate::faucet::controller::FaucetController; #[component] pub fn TransactionList( From 2d2ad7a02f74169a1ea7dfca8755b48629f935bf Mon Sep 17 00:00:00 2001 From: Shashank <shashank@chainsafe.io> Date: Wed, 28 May 2025 17:36:39 +0530 Subject: [PATCH 8/9] remove license check --- Makefile | 6 +----- scripts/add_license.sh | 19 ------------------- scripts/copyright.txt | 2 -- src/app.rs | 7 ++----- src/faucet/calibnet/mod.rs | 13 ------------- src/faucet/{mainnet/mod.rs => constants.rs} | 13 ++++++++----- src/faucet/controller.rs | 7 ++----- src/faucet/mod.rs | 6 +----- src/faucet/model.rs | 3 --- src/faucet/rate_limiter.rs | 6 +----- src/faucet/server.rs | 11 ++++------- src/faucet/views/{ => components}/alert.rs | 3 --- src/faucet/views/{ => components}/balance.rs | 3 --- src/faucet/views/{ => components}/icons.rs | 3 --- src/faucet/views/{ => components}/layout.rs | 3 --- src/faucet/views/components/mod.rs | 6 ++++++ src/faucet/views/{ => components}/nav.rs | 3 --- .../views/{ => components}/transaction.rs | 3 --- .../views.rs => views/faucets/calibnet.rs} | 7 ++----- .../views.rs => views/faucets/mainnet.rs} | 7 ++----- .../views/{faucet.rs => faucets/mod.rs} | 12 ++++++------ src/faucet/views/home.rs | 9 +++------ src/faucet/views/mod.rs | 12 ++---------- src/lib.rs | 3 --- src/utils/address.rs | 3 --- src/utils/error.rs | 3 --- src/utils/format.rs | 3 --- src/utils/key.rs | 3 --- src/utils/lotus_json/address.rs | 3 --- src/utils/lotus_json/big_int.rs | 3 --- src/utils/lotus_json/cid.rs | 3 --- src/utils/lotus_json/message.rs | 3 --- src/utils/lotus_json/mod.rs | 3 --- src/utils/lotus_json/opt.rs | 3 --- src/utils/lotus_json/raw_bytes.rs | 3 --- src/utils/lotus_json/signature.rs | 3 --- src/utils/lotus_json/signature_type.rs | 3 --- src/utils/lotus_json/signed_message.rs | 3 --- src/utils/lotus_json/token_amount.rs | 3 --- src/utils/lotus_json/vec.rs | 3 --- src/utils/lotus_json/vec_u8.rs | 3 --- src/utils/message.rs | 3 --- src/utils/mod.rs | 3 --- src/utils/rpc_context.rs | 3 --- 44 files changed, 40 insertions(+), 187 deletions(-) delete mode 100755 scripts/add_license.sh delete mode 100644 scripts/copyright.txt delete mode 100644 src/faucet/calibnet/mod.rs rename src/faucet/{mainnet/mod.rs => constants.rs} (51%) rename src/faucet/views/{ => components}/alert.rs (97%) rename src/faucet/views/{ => components}/balance.rs (95%) rename src/faucet/views/{ => components}/icons.rs (90%) rename src/faucet/views/{ => components}/layout.rs (92%) create mode 100644 src/faucet/views/components/mod.rs rename src/faucet/views/{ => components}/nav.rs (85%) rename src/faucet/views/{ => components}/transaction.rs (97%) rename src/faucet/{calibnet/views.rs => views/faucets/calibnet.rs} (88%) rename src/faucet/{mainnet/views.rs => views/faucets/mainnet.rs} (84%) rename src/faucet/views/{faucet.rs => faucets/mod.rs} (93%) diff --git a/Makefile b/Makefile index 6eac2cb9..8c504a2c 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ install-lint-tools-ci: cp cargo-binstall ~/.cargo/bin/cargo-binstall cargo +stable binstall --no-confirm cargo-spellcheck taplo-cli cargo-deny leptosfmt -lint-all: deny spellcheck fmt-lints cargo-clippy license +lint-all: deny spellcheck fmt-lints cargo-clippy fmt: cargo fmt --all @@ -41,7 +41,3 @@ deny: spellcheck: cargo spellcheck --code 1 || (echo "See .config/spellcheck.toml"; false) - -# Checks if all headers are present and adds if not -license: - ./scripts/add_license.sh diff --git a/scripts/add_license.sh b/scripts/add_license.sh deleted file mode 100755 index 5497b87a..00000000 --- a/scripts/add_license.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -# -# Checks if the source code contains required license and adds it if necessary. -# Returns 1 if there was a missing license, 0 otherwise. - -PAT_APA="^// Copyright 2019-2025 ChainSafe Systems// SPDX-License-Identifier: Apache-2.0, MIT$" - -ret=0 -for file in $(git grep --cached -Il '' -- '*.rs'); do - header=$(head -2 "$file" | tr -d '\n') - if ! echo "$header" | grep -q "$PAT_APA"; then - echo "$file was missing header" - cat ./scripts/copyright.txt "$file" > temp - mv temp "$file" - ret=1 - fi -done - -exit $ret diff --git a/scripts/copyright.txt b/scripts/copyright.txt deleted file mode 100644 index babf0bf4..00000000 --- a/scripts/copyright.txt +++ /dev/null @@ -1,2 +0,0 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT diff --git a/src/app.rs b/src/app.rs index b8462c0f..2854c789 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,8 +1,5 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - -use crate::faucet::views::{faucet::Faucets, home::Explorer, layout::Footer}; -use crate::faucet::{calibnet::views::Faucet_Calibnet, mainnet::views::Faucet_Mainnet}; +use crate::faucet::views::faucets::{calibnet::Faucet_Calibnet, mainnet::Faucet_Mainnet, Faucets}; +use crate::faucet::views::{components::layout::Footer, home::Explorer}; use crate::utils::rpc_context::RpcContext; use leptos::prelude::*; use leptos::{component, view, IntoView}; diff --git a/src/faucet/calibnet/mod.rs b/src/faucet/calibnet/mod.rs deleted file mode 100644 index 79957e4f..00000000 --- a/src/faucet/calibnet/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - -pub mod views; - -use fvm_shared::econ::TokenAmount; -use std::sync::LazyLock; - -pub const CALIBNET_RATE_LIMIT_SECONDS: i64 = 60; -pub static FIL_CALIBNET_UNIT: &str = "tFIL"; -/// The amount of mainnet FIL to be dripped to the user. This corresponds to 1 tFIL. -pub static CALIBNET_DRIP_AMOUNT: LazyLock<TokenAmount> = - LazyLock::new(|| TokenAmount::from_whole(1)); diff --git a/src/faucet/mainnet/mod.rs b/src/faucet/constants.rs similarity index 51% rename from src/faucet/mainnet/mod.rs rename to src/faucet/constants.rs index 10d6313d..8774b175 100644 --- a/src/faucet/mainnet/mod.rs +++ b/src/faucet/constants.rs @@ -1,11 +1,14 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - -pub mod views; - use fvm_shared::econ::TokenAmount; use std::sync::LazyLock; +// Calibnet constants +pub const CALIBNET_RATE_LIMIT_SECONDS: i64 = 60; +pub static FIL_CALIBNET_UNIT: &str = "tFIL"; +/// The amount of mainnet FIL to be dripped to the user. This corresponds to 1 tFIL. +pub static CALIBNET_DRIP_AMOUNT: LazyLock<TokenAmount> = + LazyLock::new(|| TokenAmount::from_whole(1)); + +// Mainnet constants pub const MAINNET_RATE_LIMIT_SECONDS: i64 = 600; pub static FIL_MAINNET_UNIT: &str = "FIL"; /// The amount of mainnet FIL to be dripped to the user. This corresponds to 0.01 FIL. diff --git a/src/faucet/controller.rs b/src/faucet/controller.rs index 8f67e478..bad9afc0 100644 --- a/src/faucet/controller.rs +++ b/src/faucet/controller.rs @@ -1,8 +1,5 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - -use super::calibnet::{CALIBNET_DRIP_AMOUNT, CALIBNET_RATE_LIMIT_SECONDS, FIL_CALIBNET_UNIT}; -use super::mainnet::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS}; +use super::constants::{CALIBNET_DRIP_AMOUNT, CALIBNET_RATE_LIMIT_SECONDS, FIL_CALIBNET_UNIT}; +use super::constants::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS}; use super::server::{faucet_address, sign_with_secret_key}; use crate::faucet::model::FaucetModel; diff --git a/src/faucet/mod.rs b/src/faucet/mod.rs index 7971118e..d7765f27 100644 --- a/src/faucet/mod.rs +++ b/src/faucet/mod.rs @@ -1,12 +1,8 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - mod controller; mod model; #[cfg(feature = "ssr")] mod rate_limiter; -pub mod calibnet; -pub mod mainnet; +pub mod constants; pub mod server; pub mod views; diff --git a/src/faucet/model.rs b/src/faucet/model.rs index c6c1e3d1..b7f9d923 100644 --- a/src/faucet/model.rs +++ b/src/faucet/model.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use cid::Cid; use fvm_shared::{address::Network, econ::TokenAmount}; use leptos::prelude::*; diff --git a/src/faucet/rate_limiter.rs b/src/faucet/rate_limiter.rs index f00b1069..9050bc0c 100644 --- a/src/faucet/rate_limiter.rs +++ b/src/faucet/rate_limiter.rs @@ -1,8 +1,4 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - -use crate::faucet::calibnet::CALIBNET_RATE_LIMIT_SECONDS; -use crate::faucet::mainnet::MAINNET_RATE_LIMIT_SECONDS; +use crate::faucet::constants::{CALIBNET_RATE_LIMIT_SECONDS, MAINNET_RATE_LIMIT_SECONDS}; use chrono::{DateTime, Duration, Utc}; use worker::*; diff --git a/src/faucet/server.rs b/src/faucet/server.rs index ea8a814e..efaa0516 100644 --- a/src/faucet/server.rs +++ b/src/faucet/server.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - #[cfg(feature = "ssr")] use crate::utils::key::{sign, Key}; use crate::utils::lotus_json::{signed_message::SignedMessage, LotusJson}; @@ -32,8 +29,8 @@ pub async fn sign_with_secret_key( let LotusJson(msg) = msg; let cid = message_cid(&msg); let amount_limit = match is_mainnet { - true => crate::faucet::mainnet::MAINNET_DRIP_AMOUNT.clone(), - false => crate::faucet::calibnet::CALIBNET_DRIP_AMOUNT.clone(), + true => crate::faucet::constants::MAINNET_DRIP_AMOUNT.clone(), + false => crate::faucet::constants::CALIBNET_DRIP_AMOUNT.clone(), }; if msg.value > amount_limit { return Err(ServerFnError::ServerError( @@ -53,9 +50,9 @@ pub async fn sign_with_secret_key( let network = if is_mainnet { "mainnet" } else { "calibnet" }; let may_sign = rate_limiter_disabled || query_rate_limiter(network).await?; let rate_limit_seconds = if is_mainnet { - crate::faucet::mainnet::MAINNET_RATE_LIMIT_SECONDS + crate::faucet::constants::MAINNET_RATE_LIMIT_SECONDS } else { - crate::faucet::calibnet::CALIBNET_RATE_LIMIT_SECONDS + crate::faucet::constants::CALIBNET_RATE_LIMIT_SECONDS }; if !may_sign { return Err(ServerFnError::ServerError(format!( diff --git a/src/faucet/views/alert.rs b/src/faucet/views/components/alert.rs similarity index 97% rename from src/faucet/views/alert.rs rename to src/faucet/views/components/alert.rs index 50a46fb3..f5756f2b 100644 --- a/src/faucet/views/alert.rs +++ b/src/faucet/views/components/alert.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use crate::faucet::controller::FaucetController; use leptos::prelude::*; use leptos::task::spawn_local; diff --git a/src/faucet/views/balance.rs b/src/faucet/views/components/balance.rs similarity index 95% rename from src/faucet/views/balance.rs rename to src/faucet/views/components/balance.rs index af1ed05f..fa0a8708 100644 --- a/src/faucet/views/balance.rs +++ b/src/faucet/views/components/balance.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use leptos::prelude::*; use leptos::{component, view, IntoView}; diff --git a/src/faucet/views/icons.rs b/src/faucet/views/components/icons.rs similarity index 90% rename from src/faucet/views/icons.rs rename to src/faucet/views/components/icons.rs index 53f6def3..d8c21d59 100644 --- a/src/faucet/views/icons.rs +++ b/src/faucet/views/components/icons.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use leptos::prelude::*; #[component] diff --git a/src/faucet/views/layout.rs b/src/faucet/views/components/layout.rs similarity index 92% rename from src/faucet/views/layout.rs rename to src/faucet/views/components/layout.rs index ea8e66be..2c81f65e 100644 --- a/src/faucet/views/layout.rs +++ b/src/faucet/views/components/layout.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use leptos::prelude::*; use leptos::{component, view, IntoView}; diff --git a/src/faucet/views/components/mod.rs b/src/faucet/views/components/mod.rs new file mode 100644 index 00000000..0c54beb1 --- /dev/null +++ b/src/faucet/views/components/mod.rs @@ -0,0 +1,6 @@ +pub mod alert; +pub mod balance; +pub mod icons; +pub mod layout; +pub mod nav; +pub mod transaction; diff --git a/src/faucet/views/nav.rs b/src/faucet/views/components/nav.rs similarity index 85% rename from src/faucet/views/nav.rs rename to src/faucet/views/components/nav.rs index 9a708476..3a025486 100644 --- a/src/faucet/views/nav.rs +++ b/src/faucet/views/components/nav.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use leptos::prelude::*; use leptos::{component, view, IntoView}; diff --git a/src/faucet/views/transaction.rs b/src/faucet/views/components/transaction.rs similarity index 97% rename from src/faucet/views/transaction.rs rename to src/faucet/views/components/transaction.rs index 36dcdbf2..37205194 100644 --- a/src/faucet/views/transaction.rs +++ b/src/faucet/views/components/transaction.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use crate::utils::format::{format_url, SearchPath}; use ::cid::Cid; use leptos::prelude::*; diff --git a/src/faucet/calibnet/views.rs b/src/faucet/views/faucets/calibnet.rs similarity index 88% rename from src/faucet/calibnet/views.rs rename to src/faucet/views/faucets/calibnet.rs index d0eebd7e..126d4158 100644 --- a/src/faucet/calibnet/views.rs +++ b/src/faucet/views/faucets/calibnet.rs @@ -1,10 +1,7 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - -use crate::faucet::calibnet::{ +use super::Faucet; +use crate::faucet::constants::{ CALIBNET_DRIP_AMOUNT, CALIBNET_RATE_LIMIT_SECONDS, FIL_CALIBNET_UNIT, }; -use crate::faucet::views::faucet::Faucet; use crate::utils::format::format_balance; use crate::utils::rpc_context::{Provider, RpcContext}; use fvm_shared::address::Network; diff --git a/src/faucet/mainnet/views.rs b/src/faucet/views/faucets/mainnet.rs similarity index 84% rename from src/faucet/mainnet/views.rs rename to src/faucet/views/faucets/mainnet.rs index 4bd01e4f..ec9597f8 100644 --- a/src/faucet/mainnet/views.rs +++ b/src/faucet/views/faucets/mainnet.rs @@ -1,8 +1,5 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - -use crate::faucet::mainnet::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS}; -use crate::faucet::views::faucet::Faucet; +use super::Faucet; +use crate::faucet::constants::{FIL_MAINNET_UNIT, MAINNET_DRIP_AMOUNT, MAINNET_RATE_LIMIT_SECONDS}; use crate::utils::format::format_balance; use crate::utils::rpc_context::{Provider, RpcContext}; use fvm_shared::address::Network; diff --git a/src/faucet/views/faucet.rs b/src/faucet/views/faucets/mod.rs similarity index 93% rename from src/faucet/views/faucet.rs rename to src/faucet/views/faucets/mod.rs index 58159ae1..20e7d484 100644 --- a/src/faucet/views/faucet.rs +++ b/src/faucet/views/faucets/mod.rs @@ -1,5 +1,5 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT +pub mod calibnet; +pub mod mainnet; use fvm_shared::address::Network; use leptos::prelude::*; @@ -10,10 +10,10 @@ use leptos_use::use_interval_fn; use url::Url; use crate::faucet::controller::FaucetController; -use crate::faucet::views::alert::ErrorMessages; -use crate::faucet::views::balance::{FaucetBalance, TargetBalance}; -use crate::faucet::views::nav::{GotoFaucetList, GotoHome}; -use crate::faucet::views::transaction::{TransactionHistoryButton, TransactionList}; +use crate::faucet::views::components::alert::ErrorMessages; +use crate::faucet::views::components::balance::{FaucetBalance, TargetBalance}; +use crate::faucet::views::components::nav::{GotoFaucetList, GotoHome}; +use crate::faucet::views::components::transaction::{TransactionHistoryButton, TransactionList}; #[component] fn FaucetInput(faucet: RwSignal<FaucetController>) -> impl IntoView { diff --git a/src/faucet/views/home.rs b/src/faucet/views/home.rs index 5062da92..2f58a59a 100644 --- a/src/faucet/views/home.rs +++ b/src/faucet/views/home.rs @@ -1,9 +1,6 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - -use crate::faucet::views::icons::{CheckIcon, LightningIcon, Loader}; -use crate::faucet::views::layout::Header; -use crate::faucet::views::nav::GotoFaucetList; +use crate::faucet::views::components::icons::{CheckIcon, LightningIcon, Loader}; +use crate::faucet::views::components::layout::Header; +use crate::faucet::views::components::nav::GotoFaucetList; use crate::utils::rpc_context::{Provider, RpcContext}; use fvm_shared::address::Network; use leptos::prelude::*; diff --git a/src/faucet/views/mod.rs b/src/faucet/views/mod.rs index 89705409..283ddb2e 100644 --- a/src/faucet/views/mod.rs +++ b/src/faucet/views/mod.rs @@ -1,11 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - -pub mod alert; -pub mod balance; -pub mod faucet; +pub mod components; +pub mod faucets; pub mod home; -pub mod icons; -pub mod layout; -pub mod nav; -pub mod transaction; diff --git a/src/lib.rs b/src/lib.rs index 7c663cc4..520857dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - mod app; mod utils; #[cfg(feature = "hydrate")] diff --git a/src/utils/address.rs b/src/utils/address.rs index 1cf5ac45..2ab8d38d 100644 --- a/src/utils/address.rs +++ b/src/utils/address.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use anyhow::ensure; use fvm_shared::address::{Address, Network}; use fvm_shared::ActorID; diff --git a/src/utils/error.rs b/src/utils/error.rs index 23ed8ba8..a0b96f12 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use leptos::prelude::{RwSignal, Update}; use std::future::Future; use uuid::Uuid; diff --git a/src/utils/format.rs b/src/utils/format.rs index a51fa0c2..3844633e 100644 --- a/src/utils/format.rs +++ b/src/utils/format.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use anyhow::{anyhow, Result}; use fvm_shared::econ::TokenAmount; use url::Url; diff --git a/src/utils/key.rs b/src/utils/key.rs index cb0db1eb..cb9747f2 100644 --- a/src/utils/key.rs +++ b/src/utils/key.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use anyhow::{Context as _, Result}; use bls_signatures::{PrivateKey as BlsPrivate, Serialize as _}; use libsecp256k1::{PublicKey as SecpPublic, SecretKey as SecpPrivate}; diff --git a/src/utils/lotus_json/address.rs b/src/utils/lotus_json/address.rs index 5738d352..2ac97c89 100644 --- a/src/utils/lotus_json/address.rs +++ b/src/utils/lotus_json/address.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::*; use fvm_shared::address::{Address, Network}; diff --git a/src/utils/lotus_json/big_int.rs b/src/utils/lotus_json/big_int.rs index 61722b64..2e1f1909 100644 --- a/src/utils/lotus_json/big_int.rs +++ b/src/utils/lotus_json/big_int.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::*; use fvm_shared::bigint::BigInt; diff --git a/src/utils/lotus_json/cid.rs b/src/utils/lotus_json/cid.rs index b59a72b6..aa2fa8da 100644 --- a/src/utils/lotus_json/cid.rs +++ b/src/utils/lotus_json/cid.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::*; #[derive(Clone, Serialize, Deserialize)] diff --git a/src/utils/lotus_json/message.rs b/src/utils/lotus_json/message.rs index 46dabb93..dd874280 100644 --- a/src/utils/lotus_json/message.rs +++ b/src/utils/lotus_json/message.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::*; use fvm_ipld_encoding::RawBytes; use fvm_shared::{address::Address, econ::TokenAmount, message::Message}; diff --git a/src/utils/lotus_json/mod.rs b/src/utils/lotus_json/mod.rs index 873eff74..4b09ec57 100644 --- a/src/utils/lotus_json/mod.rs +++ b/src/utils/lotus_json/mod.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - //! In the Filecoin ecosystem, there are TWO different ways to present a domain object: //! - CBOR (defined in [`fvm_ipld_encoding`]). //! This is the wire format. diff --git a/src/utils/lotus_json/opt.rs b/src/utils/lotus_json/opt.rs index 3e16f5fd..40447fd6 100644 --- a/src/utils/lotus_json/opt.rs +++ b/src/utils/lotus_json/opt.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::*; // TODO(forest): https://github.com/ChainSafe/forest/issues/4032 diff --git a/src/utils/lotus_json/raw_bytes.rs b/src/utils/lotus_json/raw_bytes.rs index e9991180..a89ae924 100644 --- a/src/utils/lotus_json/raw_bytes.rs +++ b/src/utils/lotus_json/raw_bytes.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::{vec_u8::VecU8LotusJson, *}; use fvm_ipld_encoding::RawBytes; diff --git a/src/utils/lotus_json/signature.rs b/src/utils/lotus_json/signature.rs index a348b5ae..3f3a390b 100644 --- a/src/utils/lotus_json/signature.rs +++ b/src/utils/lotus_json/signature.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::*; use fvm_shared::crypto::signature::{Signature, SignatureType}; diff --git a/src/utils/lotus_json/signature_type.rs b/src/utils/lotus_json/signature_type.rs index bbc15b88..76aeabee 100644 --- a/src/utils/lotus_json/signature_type.rs +++ b/src/utils/lotus_json/signature_type.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::*; use fvm_shared::crypto::signature::SignatureType; diff --git a/src/utils/lotus_json/signed_message.rs b/src/utils/lotus_json/signed_message.rs index dcfa6ef9..f32c7bf4 100644 --- a/src/utils/lotus_json/signed_message.rs +++ b/src/utils/lotus_json/signed_message.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::HasLotusJson; use ::cid::Cid; use fvm_ipld_encoding::Error; diff --git a/src/utils/lotus_json/token_amount.rs b/src/utils/lotus_json/token_amount.rs index add0de07..d7d0e19c 100644 --- a/src/utils/lotus_json/token_amount.rs +++ b/src/utils/lotus_json/token_amount.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::*; use fvm_shared::bigint::BigInt; use fvm_shared::econ::TokenAmount; diff --git a/src/utils/lotus_json/vec.rs b/src/utils/lotus_json/vec.rs index 49f3aa47..56ba8da7 100644 --- a/src/utils/lotus_json/vec.rs +++ b/src/utils/lotus_json/vec.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::*; impl<T> HasLotusJson for Vec<T> diff --git a/src/utils/lotus_json/vec_u8.rs b/src/utils/lotus_json/vec_u8.rs index 58bbe2d5..19b9b3a3 100644 --- a/src/utils/lotus_json/vec_u8.rs +++ b/src/utils/lotus_json/vec_u8.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use super::*; // This code looks odd so we can diff --git a/src/utils/message.rs b/src/utils/message.rs index 9c2a040e..74452cce 100644 --- a/src/utils/message.rs +++ b/src/utils/message.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use fvm_ipld_encoding::RawBytes; use fvm_shared::message::Message; use fvm_shared::{address::Address, econ::TokenAmount, METHOD_SEND}; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 8efe44d5..ef28000c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - pub mod address; pub mod error; pub mod format; diff --git a/src/utils/rpc_context.rs b/src/utils/rpc_context.rs index 445378c6..7ba72b9b 100644 --- a/src/utils/rpc_context.rs +++ b/src/utils/rpc_context.rs @@ -1,6 +1,3 @@ -// Copyright 2019-2025 ChainSafe Systems -// SPDX-License-Identifier: Apache-2.0, MIT - use cid::Cid; use fvm_shared::address::{set_current_network, Address, Network}; use fvm_shared::econ::TokenAmount; From a36aa00b21842d9e40b59d6e5da5d0d896e5aa22 Mon Sep 17 00:00:00 2001 From: Shashank <shashank@chainsafe.io> Date: Wed, 28 May 2025 17:40:30 +0530 Subject: [PATCH 9/9] update diagram --- docs/components.md | 110 +++++++++++++++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 29 deletions(-) diff --git a/docs/components.md b/docs/components.md index 824e9e41..1ab60225 100644 --- a/docs/components.md +++ b/docs/components.md @@ -1,33 +1,85 @@ Forest Explorer Components Diagram ```mermaid -graph TD - subgraph src["Forest Explorer"] - subgraph utils["Utils"] - message["Message transfer<br/>message.rs"] - format["Formatting balance & URL<br/>format.rs"] - key["KeyPair<br/>key.rs"] - error["Error handler<br/>error.rs"] - address["Address utils<br/>address.rs"] - rpc_context["Supported RPC's<br/>rpc_context.rs"] - lotus_json["Lotus JSON<br/>lotus_json/"] - end - subgraph faucet["Faucet"] - model["Faucet Model<br/>model.rs"] - model["Faucet Controller<br/>controller.rs"] - rate_limiter["Rate Limiter<br/>rate_limiter.rs"] - calibnet["Calibnet Faucet Specific<br/>/faucet/calibnet/"] - mainnet["Mainnet Faucet Specific<br/>/faucet/mainnet/"] - subgraph views["UI Components"] - home["Home page view<br/>home.rs"] - layout["Page layouts view<br/>layout.rs"] - faucet_view["Faucet page view<br/>faucet.rs"] - transaction["Transactions view<br/>transaction.rs"] - balance["Balance view<br/>balance.rs"] - alert["Error view<br/>alert.rs"] - nav["Navigation view<br/>nav.rs"] - icons["Icons<br/>icons.rs"] - end - end - end +flowchart TD + %% Forest Explorer Components + subgraph Main + direction TB + App[App] + Ctrl[Faucet Controller] + Model[Faucet Model] + Server[SSR Logic] + RateLimiter[Rate Limiter] + Constants[Network Constants] + end + + %% UI + subgraph UI + direction TB + Views[Views] + Home[Home] + Faucets[Faucets] + Components[UI Components] + end + + %% Sub-sections of UI + subgraph Faucets + direction TB + Mainnet[Mainnet] + Calibnet[Calibnet] + end + subgraph Components + direction TB + Layout[Layout] + Balance[Balance] + Transaction[Transaction] + Icon[Icon] + Alert[Alert] + Nav[Navigation] + end + + %% Utilities + subgraph Utils + direction TB + Addr[Address] + Key + Fmt[Format] + Err[Errors] + Msg[Message] + RpcCtx[RPC Context] + LotusJson[Lotus JSON] + end + + %% Main relations + App --> Ctrl + App --> Server + Ctrl --> Model + Ctrl --> RateLimiter + Ctrl --> Constants + Ctrl --> Utils + Ctrl --> Views + Server --> Utils + + %% UI relations + Views --> Faucets + Views --> Components + + %% UI sub-relations + Faucets --> Mainnet + Faucets --> Calibnet + Components --> Layout + Components --> Balance + Components --> Transaction + Components --> Icon + Components --> Alert + Components --> Nav + + %% Utilities relations + Utils --> LotusJson + Utils --> Addr + Utils --> Key + Utils --> Fmt + Utils --> Err + Utils --> Msg + Utils --> RpcCtx ```