Skip to content

add faucet page#31

Merged
tac0turtle merged 10 commits intomainfrom
marko/faucet
Mar 18, 2026
Merged

add faucet page#31
tac0turtle merged 10 commits intomainfrom
marko/faucet

Conversation

@tac0turtle
Copy link
Contributor

@tac0turtle tac0turtle commented Mar 18, 2026

Overview

closes ##28

Summary by CodeRabbit

  • New Features

    • Faucet UI and route to request test ETH, showing balance, drip amount, cooldown, transaction link on success, and conditional navigation when available.
    • Client-side hook and API client for faucet interactions.
  • Behavioral / API

    • Rate-limit responses surface retry info (body + header) and client error handling now exposes retry seconds/status for cooldown UX.
  • Configuration

    • New optional environment variables to enable and configure the faucet (enabled, private key, amount, cooldown).

@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

Warning

Rate limit exceeded

@tac0turtle has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 15 minutes and 4 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc620911-a204-4cdc-bcbb-db536d85d826

📥 Commits

Reviewing files that changed from the base of the PR and between 5469152 and 636c4e4.

📒 Files selected for processing (5)
  • backend/crates/atlas-server/src/api/handlers/faucet.rs
  • backend/crates/atlas-server/src/config.rs
  • frontend/src/components/Layout.tsx
  • frontend/src/context/FaucetInfoContext.tsx
  • frontend/src/pages/FaucetPage.tsx
📝 Walkthrough

Walkthrough

Adds an optional faucet feature: configuration, backend FaucetService with per-address/IP cooldowns and 429 handling, API endpoints (/api/faucet/info, POST /api/faucet), router/state integration, frontend pages/hooks/clients, Axios error enhancements, and docker/.env examples plus dependency updates.

Changes

Cohort / File(s) Summary
Config & Manifests
\.env.example, docker-compose.yml, Justfile, backend/Cargo.toml, backend/crates/atlas-server/Cargo.toml
Adds FAUCET_* env examples and docker/justfile vars; adds tower dependency to workspace and atlas-server crate.
Error handling
backend/crates/atlas-common/src/error.rs, backend/crates/atlas-server/src/api/error.rs
Introduces TooManyRequests AtlasError variant, maps to HTTP 429, includes Retry-After header and retry_after_seconds JSON field; adds unit test.
Faucet core
backend/crates/atlas-server/src/faucet.rs, backend/crates/atlas-server/src/config.rs
New faucet backend: FaucetService, FaucetBackend trait, FaucetInfo/FaucetTxResponse types, cooldown store/reservations, helper funcs, FaucetConfig parsing from env (amount→wei, cooldown), and tests.
API handlers & router
backend/crates/atlas-server/src/api/handlers/faucet.rs, backend/crates/atlas-server/src/api/handlers/mod.rs, backend/crates/atlas-server/src/api/mod.rs, backend/crates/atlas-server/src/api/handlers/status.rs
Adds faucet handlers (GET info, POST request), client IP extraction/normalization, conditional route mounting when faucet present, AppState.faucet field, and tests with FakeFaucet.
App init
backend/crates/atlas-server/src/main.rs
Loads FaucetConfig, conditionally initializes FaucetService (PrivateKeySigner + Provider), and attaches optional faucet backend to AppState.
Frontend API & client
frontend/src/api/client.ts, frontend/src/api/faucet.ts, frontend/src/api/index.ts
Adds faucet API client functions; Axios interceptor now parses retry-after from headers/body and surfaces status & retryAfterSeconds in ApiError.
Frontend hooks, types & utils
frontend/src/hooks/useFaucetInfo.ts, frontend/src/hooks/index.ts, frontend/src/types/index.ts, frontend/src/utils/apiError.ts, frontend/src/utils/index.ts
Adds useFaucetInfo hook, Faucet types (FaucetInfo, FaucetRequestResponse), toApiError normalizer, and re-exports.
Frontend UI & routing
frontend/src/pages/FaucetPage.tsx, frontend/src/pages/index.ts, frontend/src/components/Layout.tsx, frontend/src/App.tsx
New FaucetPage UI, conditional nav link based on faucet availability, and client-side route mounting.
Misc frontend
frontend/src/pages/AddressPage.tsx, frontend/package.json
Replaced dynamic import with static import for getToken; bumped axios ^1.13.2 → ^1.13.6.
Small exports
backend/crates/atlas-server/src/api/handlers/mod.rs
Exports new faucet handler module.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser
    participant UI as FaucetPage
    participant Client as AxiosClient
    participant Server as AtlasServer
    participant Faucet as FaucetService
    participant Provider as BlockchainProvider

    Browser->>UI: user submits address
    UI->>Client: POST /api/faucet (includes client IP headers)
    Client->>Server: HTTP POST /api/faucet
    Server->>Faucet: request_faucet(address, client_ip)
    alt cooldown active
        Faucet-->>Server: TooManyRequests(retry_after_seconds)
        Server-->>Client: 429 + Retry-After header + body { retry_after_seconds }
        Client-->>UI: ApiError { retryAfterSeconds }
        UI->>UI: display cooldown/countdown
    else allowed
        Faucet->>Provider: send transaction(amount_wei)
        Provider-->>Faucet: tx_hash
        Faucet->>Faucet: record cooldown for address/IP
        Faucet-->>Server: FaucetTxResponse { tx_hash }
        Server-->>Client: 200 + body
        Client-->>UI: success (tx_hash)
        UI->>UI: show tx link and start cooldown UI
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • pthmas

Poem

🐇 I hopped through code with tiny cheer,

Dripped out test ETH for friends far and near.
Cooldowns guard the garden gate,
Routes and UI now celebrate.
Hooray — a faucet, calm and clear!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'add faucet page' accurately summarizes the main change - introducing a new faucet page component with full frontend and backend functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch marko/faucet
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
frontend/src/pages/AddressPage.tsx (1)

41-62: Consider parallel fetching for improved performance.

The sequential for loop fetches tokens one at a time. If an address has many unique ERC-20 contracts, this could be noticeably slower. The current approach does have the benefit of isolated error handling per token.

♻️ Optional: Parallel fetch with Promise.allSettled
   useEffect(() => {
     let cancelled = false;
     (async () => {
       const unique = Array.from(new Set(
         transfers
           .filter(t => t.transfer_type === 'erc20')
           .map(t => t.contract_address.toLowerCase())
       ));
       if (unique.length === 0) return;
-      const updates: Record<string, { decimals: number }> = {};
-      for (const addr of unique) {
-        try {
-          const t = await getToken(addr);
-          updates[addr] = { decimals: t.decimals };
-        } catch {
-          updates[addr] = { decimals: 18 };
-        }
-      }
+      const results = await Promise.allSettled(unique.map(addr => getToken(addr)));
+      const updates: Record<string, { decimals: number }> = {};
+      unique.forEach((addr, i) => {
+        const result = results[i];
+        updates[addr] = { decimals: result.status === 'fulfilled' ? result.value.decimals : 18 };
+      });
       if (!cancelled) setTokenMeta(prev => ({ ...prev, ...updates }));
     })();
     return () => { cancelled = true; };
   }, [transfers]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/AddressPage.tsx` around lines 41 - 62, The effect
currently fetches token metadata sequentially in the for loop which is slow for
many addresses; change it to run parallel requests using Promise.allSettled on
unique (from transfers) mapped to getToken(addr), then build the same updates
object by iterating the settled results (on fulfilled use result.value.decimals,
on rejected set 18), and finally, if not cancelled call setTokenMeta(prev => ({
...prev, ...updates })); keep the cancelled flag and the same variable names
(useEffect, transfers, getToken, updates, setTokenMeta) and preserve per-token
error handling by interpreting rejected promises as default decimals: 18.
frontend/src/components/Layout.tsx (1)

15-15: Avoid global unconditional faucet info fetch in layout.

Line 15 triggers useFaucetInfo() for all app visits; with current hook behavior this performs /faucet/info fetch on layout mount even outside faucet flows, and can duplicate calls with faucet page data loading. Consider moving this to shared cached state (or route-level prefetch) to avoid redundant network traffic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/Layout.tsx` at line 15, The Layout component
currently calls useFaucetInfo() unconditionally (const { faucetInfo } =
useFaucetInfo()), causing a global /faucet/info fetch on every mount and
duplicate requests; remove that top-level call from Layout.tsx and instead load
faucet info from a shared cached source or at route level: either (a) wire
useFaucetInfo into a Faucet-specific page or route loader so the hook runs only
on faucet routes, or (b) move the fetch into a global cache/provider (e.g.,
React Context or react-query with a single query key) and have useFaucetInfo
read from that cache without issuing a new network request. Update consumers to
read from the shared cache/provider or from the faucet route prefetch so
duplicate network calls are avoided.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/crates/atlas-server/src/api/handlers/faucet.rs`:
- Around line 46-68: The current extract_client_ip function trusts the left-most
X-Forwarded-For hop (first item) which is spoofable because nginx appends the
real client IP to the end; change extract_client_ip to choose the last non-empty
hop from X-Forwarded-For (i.e., split on ',' and take the last trimmed entry)
before calling normalize_ip, and fall back to X-Real-IP as before; update any
tests to add a regression test feeding an X-Forwarded-For value like
"203.0.113.10, 127.0.0.1" and assert the resolved client IP is the right-most
hop (127.0.0.1) after normalize_ip.

In `@backend/crates/atlas-server/src/api/mod.rs`:
- Around line 147-150: The TimeoutLayer is currently applied before the
conditional faucet routes are registered, so the `/api/faucet/*` endpoints are
left unprotected; move the
TimeoutLayer::with_status_code(Duration::from_secs(10),
axum::http::StatusCode::REQUEST_TIMEOUT) call so it is applied after the faucet
route registration block (the code that conditionally inserts the faucet
service) and before the SSE merge/merge method is called, ensuring TimeoutLayer
wraps all routes including the faucet handlers; update the builder chain to
place TimeoutLayer immediately after the faucet registration and before the SSE
merge/merge call (refer to TimeoutLayer and the faucet route registration block
in mod.rs).

---

Nitpick comments:
In `@frontend/src/components/Layout.tsx`:
- Line 15: The Layout component currently calls useFaucetInfo() unconditionally
(const { faucetInfo } = useFaucetInfo()), causing a global /faucet/info fetch on
every mount and duplicate requests; remove that top-level call from Layout.tsx
and instead load faucet info from a shared cached source or at route level:
either (a) wire useFaucetInfo into a Faucet-specific page or route loader so the
hook runs only on faucet routes, or (b) move the fetch into a global
cache/provider (e.g., React Context or react-query with a single query key) and
have useFaucetInfo read from that cache without issuing a new network request.
Update consumers to read from the shared cache/provider or from the faucet route
prefetch so duplicate network calls are avoided.

In `@frontend/src/pages/AddressPage.tsx`:
- Around line 41-62: The effect currently fetches token metadata sequentially in
the for loop which is slow for many addresses; change it to run parallel
requests using Promise.allSettled on unique (from transfers) mapped to
getToken(addr), then build the same updates object by iterating the settled
results (on fulfilled use result.value.decimals, on rejected set 18), and
finally, if not cancelled call setTokenMeta(prev => ({ ...prev, ...updates }));
keep the cancelled flag and the same variable names (useEffect, transfers,
getToken, updates, setTokenMeta) and preserve per-token error handling by
interpreting rejected promises as default decimals: 18.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 464499c3-4369-4158-842f-9b3a3d573407

📥 Commits

Reviewing files that changed from the base of the PR and between 6cafa1e and 09c4c23.

⛔ Files ignored due to path filters (1)
  • frontend/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (27)
  • .env.example
  • backend/Cargo.toml
  • backend/crates/atlas-common/src/error.rs
  • backend/crates/atlas-server/Cargo.toml
  • backend/crates/atlas-server/src/api/error.rs
  • backend/crates/atlas-server/src/api/handlers/faucet.rs
  • backend/crates/atlas-server/src/api/handlers/mod.rs
  • backend/crates/atlas-server/src/api/handlers/status.rs
  • backend/crates/atlas-server/src/api/mod.rs
  • backend/crates/atlas-server/src/config.rs
  • backend/crates/atlas-server/src/faucet.rs
  • backend/crates/atlas-server/src/main.rs
  • docker-compose.yml
  • frontend/package.json
  • frontend/src/App.tsx
  • frontend/src/api/client.ts
  • frontend/src/api/faucet.ts
  • frontend/src/api/index.ts
  • frontend/src/components/Layout.tsx
  • frontend/src/hooks/index.ts
  • frontend/src/hooks/useFaucetInfo.ts
  • frontend/src/pages/AddressPage.tsx
  • frontend/src/pages/FaucetPage.tsx
  • frontend/src/pages/index.ts
  • frontend/src/types/index.ts
  • frontend/src/utils/apiError.ts
  • frontend/src/utils/index.ts

@tac0turtle
Copy link
Contributor Author

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 09c4c2333e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@tac0turtle
Copy link
Contributor Author

Screenshot 2026-03-18 at 15 03 33

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
frontend/src/pages/FaucetPage.tsx (1)

92-137: Consider clearing error state when address changes for better UX.

Currently, validation and submission errors persist until the next submit attempt. Users might expect the error to clear when they start editing the address. This is a minor UX enhancement.

✨ Optional: Clear error on input change

Add an onChange handler or modify the existing one at line 177:

-                onChange={(event) => setAddress(event.target.value)}
+                onChange={(event) => {
+                  setAddress(event.target.value);
+                  if (submitError) setSubmitError(null);
+                }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/FaucetPage.tsx` around lines 92 - 137, The form keeps
validation/submission errors until the next submit; update the address input
change handler (where address state is set) to clear errors so they disappear as
the user edits: call setSubmitError(null) (and optionally setTxHash(null))
inside the existing onChange that updates address, ensuring you modify the same
component that defines handleSubmit and uses setSubmitError, setTxHash, and
address so errors clear immediately when the user types.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/src/pages/FaucetPage.tsx`:
- Around line 92-137: The form keeps validation/submission errors until the next
submit; update the address input change handler (where address state is set) to
clear errors so they disappear as the user edits: call setSubmitError(null) (and
optionally setTxHash(null)) inside the existing onChange that updates address,
ensuring you modify the same component that defines handleSubmit and uses
setSubmitError, setTxHash, and address so errors clear immediately when the user
types.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 146dc211-929b-4262-b509-69f1d6e82fd1

📥 Commits

Reviewing files that changed from the base of the PR and between 09c4c23 and 2ed2e14.

📒 Files selected for processing (1)
  • frontend/src/pages/FaucetPage.tsx

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
backend/crates/atlas-server/src/api/handlers/faucet.rs (1)

78-82: Consider normalizing IPv4-mapped IPv6 addresses to IPv4.

The current implementation preserves the distinction between 127.0.0.1 and ::ffff:127.0.0.1, which represent the same client. While nginx typically uses a consistent format, explicitly normalizing IPv4-mapped IPv6 addresses would strengthen rate-limiting.

♻️ Proposed enhancement
 fn normalize_ip(ip: &str) -> Result<String, AtlasError> {
     let parsed = IpAddr::from_str(ip)
         .map_err(|_| AtlasError::InvalidInput("Invalid client IP address".to_string()))?;
-    Ok(parsed.to_string())
+    // Convert IPv4-mapped IPv6 addresses to their IPv4 equivalent
+    let canonical = match parsed {
+        IpAddr::V6(v6) => v6
+            .to_ipv4_mapped()
+            .map(IpAddr::V4)
+            .unwrap_or(IpAddr::V6(v6)),
+        other => other,
+    };
+    Ok(canonical.to_string())
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/crates/atlas-server/src/api/handlers/faucet.rs` around lines 78 - 82,
normalize_ip currently returns the parsed IpAddr string verbatim, which keeps
IPv4-mapped IPv6 addresses like "::ffff:127.0.0.1" distinct from "127.0.0.1";
update normalize_ip to detect an IpAddr::V6 that represents an IPv4-mapped
address and convert it to the equivalent IpAddr::V4 before returning.
Concretely, after parsing into IpAddr in normalize_ip, check for the V6 variant
and call the appropriate conversion (e.g., use the Ipv6 -> Ipv4 conversion
helper / to_ipv4() or equivalent) to produce an IpAddr::V4 and return its
to_string(), otherwise return the original parsed address string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/crates/atlas-server/src/api/handlers/faucet.rs`:
- Around line 78-82: normalize_ip currently returns the parsed IpAddr string
verbatim, which keeps IPv4-mapped IPv6 addresses like "::ffff:127.0.0.1"
distinct from "127.0.0.1"; update normalize_ip to detect an IpAddr::V6 that
represents an IPv4-mapped address and convert it to the equivalent IpAddr::V4
before returning. Concretely, after parsing into IpAddr in normalize_ip, check
for the V6 variant and call the appropriate conversion (e.g., use the Ipv6 ->
Ipv4 conversion helper / to_ipv4() or equivalent) to produce an IpAddr::V4 and
return its to_string(), otherwise return the original parsed address string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5b7f694b-40b3-4007-914b-bc0cc6856eae

📥 Commits

Reviewing files that changed from the base of the PR and between 2ed2e14 and 4d8080a.

📒 Files selected for processing (2)
  • backend/crates/atlas-server/src/api/handlers/faucet.rs
  • backend/crates/atlas-server/src/api/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/crates/atlas-server/src/api/mod.rs

Copy link
Collaborator

@pthmas pthmas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left just one nit.
Threw the PR into claude to review the frontend and go this:

useFaucetInfo fires on every page load via Layout — Layout.tsx
Since Layout renders on every route, GET /api/faucet/info is called on every navigation. For a disabled faucet (404 response), this is a wasted request on every page. Consider caching the 404 result (e.g., in a context or with a stale-while-revalidate approach) rather than re-fetching each time.

Might be worth investigating.

use tower::util::ServiceExt;

#[derive(Clone)]
struct FakeFaucet;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicate of the FakeFaucet in faucet.rs could we have a single definition?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/crates/atlas-server/src/api/mod.rs`:
- Around line 16-24: AppState is missing the required solc_path and
admin_api_key fields; update the AppState struct to include pub solc_path:
String and pub admin_api_key: Option<String>, and then propagate these values
through the server state construction where AppState is instantiated (look for
functions/constructors that build AppState or return the app state, e.g., any
new_app_state/build_app_state/initialize_app_state or the code that previously
set pool, block_events_tx, rpc_url, chain_id, chain_name), ensuring the API uses
the API-specific PgPool and that solc_path and admin_api_key are sourced from
configuration/environment and passed into the AppState creation call.

In `@backend/crates/atlas-server/src/config.rs`:
- Around line 39-45: FaucetConfig currently derives Debug while storing the
sensitive private_key field, risking secret leakage; remove #[derive(Debug)]
from FaucetConfig and either implement a manual impl Debug for FaucetConfig that
redacts or omits the private_key field, or drop Debug entirely for this struct;
locate the FaucetConfig definition and replace the derive with a safe manual
Debug implementation (or no Debug) so that private_key is not printed or logged.

In `@backend/crates/atlas-server/src/main.rs`:
- Around line 62-85: The new conditional faucet startup logic (using
faucet_config = config::FaucetConfig::from_env(), the if faucet_config.enabled
branch that constructs PrivateKeySigner,
ProviderBuilder::new().wallet(...).connect_http(...), and FaucetService::new as
Arc<dyn faucet::FaucetBackend>) lacks unit tests; add a #[cfg(test)] mod tests
in this file that extracts a small helper to invoke the faucet initialization
logic with a provided FaucetConfig and returns whether the service was created
or an error, and then add tests for (1) disabled faucet -> returns None, (2)
enabled faucet with valid fields -> returns Some(Arc<FaucetService>), and (3)
enabled faucet with invalid rpc_url -> returns an Err from the
connect_http/parse step; use mocked or minimal valid private_key and
amount/cooldown values and assert outcomes via the helper so tests run under
cargo test --workspace.

In `@Justfile`:
- Around line 116-119: Remove the hardcoded test faucet private key by deleting
the FAUCET_PRIVATE_KEY assignment in the Justfile test-run section and make the
key required from the caller environment: stop setting FAUCET_PRIVATE_KEY=0x...
in the Justfile, instead reference the environment variable FAUCET_PRIVATE_KEY
(and add a runtime check in the test-run task/recipe to fail with a clear error
if FAUCET_PRIVATE_KEY is unset), and ensure any documentation/examples instruct
callers to export their own key before running.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cb18361b-d2a4-47f0-ba56-3a85c61da524

📥 Commits

Reviewing files that changed from the base of the PR and between 4d8080a and 5469152.

📒 Files selected for processing (11)
  • .env.example
  • Justfile
  • backend/crates/atlas-server/src/api/handlers/mod.rs
  • backend/crates/atlas-server/src/api/handlers/status.rs
  • backend/crates/atlas-server/src/api/mod.rs
  • backend/crates/atlas-server/src/config.rs
  • backend/crates/atlas-server/src/main.rs
  • docker-compose.yml
  • frontend/src/App.tsx
  • frontend/src/components/Layout.tsx
  • frontend/src/pages/index.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • frontend/src/App.tsx
  • docker-compose.yml
  • backend/crates/atlas-server/src/api/handlers/status.rs
  • frontend/src/components/Layout.tsx

Comment on lines 16 to 24
pub struct AppState {
pub pool: PgPool,
pub block_events_tx: broadcast::Sender<()>,
pub head_tracker: Arc<HeadTracker>,
pub rpc_url: String,
pub faucet: Option<SharedFaucetBackend>,
pub chain_id: u64,
pub chain_name: String,
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

AppState is still missing required solc_path and admin_api_key fields.

Since this PR updates AppState, please align it with the required API state contract and wire these fields through state construction as well.

🧩 Suggested shape update
 pub struct AppState {
     pub pool: PgPool,
     pub block_events_tx: broadcast::Sender<()>,
     pub head_tracker: Arc<HeadTracker>,
     pub rpc_url: String,
+    pub solc_path: String,
+    pub admin_api_key: Option<String>,
     pub faucet: Option<SharedFaucetBackend>,
     pub chain_id: u64,
     pub chain_name: String,
 }

As per coding guidelines backend/crates/atlas-server/src/api/**/*.rs: Configure AppState to include pool: PgPool (API pool only), block_events_tx: broadcast::Sender<()>, rpc_url: String, solc_path: String, and optional admin_api_key.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/crates/atlas-server/src/api/mod.rs` around lines 16 - 24, AppState is
missing the required solc_path and admin_api_key fields; update the AppState
struct to include pub solc_path: String and pub admin_api_key: Option<String>,
and then propagate these values through the server state construction where
AppState is instantiated (look for functions/constructors that build AppState or
return the app state, e.g., any
new_app_state/build_app_state/initialize_app_state or the code that previously
set pool, block_events_tx, rpc_url, chain_id, chain_name), ensuring the API uses
the API-specific PgPool and that solc_path and admin_api_key are sourced from
configuration/environment and passed into the AppState creation call.

Comment on lines +62 to +85
let faucet_config = config::FaucetConfig::from_env()?;

let faucet = if faucet_config.enabled {
tracing::info!("Faucet enabled");
let private_key = faucet_config
.private_key
.as_ref()
.expect("validated faucet private key");
let signer: PrivateKeySigner = private_key.parse().expect("validated faucet private key");
let rpc_url: reqwest::Url = config
.rpc_url
.parse()
.map_err(|e| anyhow::anyhow!("Invalid RPC_URL for faucet: {e}"))?;
let provider = ProviderBuilder::new().wallet(signer).connect_http(rpc_url);
Some(Arc::new(faucet::FaucetService::new(
provider,
faucet_config.amount_wei.expect("validated faucet amount"),
faucet_config
.cooldown_minutes
.expect("validated faucet cooldown"),
)) as Arc<dyn faucet::FaucetBackend>)
} else {
None
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add unit coverage for the new faucet startup branch.

This block introduces new conditional startup logic but currently has no targeted tests in this file. Please add tests for enabled/disabled faucet paths (and invalid faucet RPC URL parse path) via a small extracted helper.

As per coding guidelines backend/crates/**/*.rs: Add unit tests for new logic in a #[cfg(test)] mod tests block in the same file, run with cargo test --workspace.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/crates/atlas-server/src/main.rs` around lines 62 - 85, The new
conditional faucet startup logic (using faucet_config =
config::FaucetConfig::from_env(), the if faucet_config.enabled branch that
constructs PrivateKeySigner,
ProviderBuilder::new().wallet(...).connect_http(...), and FaucetService::new as
Arc<dyn faucet::FaucetBackend>) lacks unit tests; add a #[cfg(test)] mod tests
in this file that extracts a small helper to invoke the faucet initialization
logic with a provided FaucetConfig and returns whether the service was created
or an error, and then add tests for (1) disabled faucet -> returns None, (2)
enabled faucet with valid fields -> returns Some(Arc<FaucetService>), and (3)
enabled faucet with invalid rpc_url -> returns an Err from the
connect_http/parse step; use mocked or minimal valid private_key and
amount/cooldown values and assert outcomes via the helper so tests run under
cargo test --workspace.

@tac0turtle tac0turtle merged commit 183345b into main Mar 18, 2026
8 checks passed
@tac0turtle tac0turtle deleted the marko/faucet branch March 18, 2026 16:52
This was referenced Mar 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants