diff --git a/docs/api-documentation.md b/docs/api-documentation.md index 0716b2cb..f6cee062 100644 --- a/docs/api-documentation.md +++ b/docs/api-documentation.md @@ -12,11 +12,6 @@ interact with the network. Users provide a valid wallet address and specify the token type (`faucet_info`) they wish to receive. On success, the API returns a transaction hash confirming the token transfer. -**Key points:** - -- Each address is subject to rate limiting to prevent abuse. -- This API only distributes Calibnet `tFIL` and `tUSDFC` tokens. - --- ## Query Parameters @@ -28,18 +23,6 @@ transaction hash confirming the token transfer. --- -## Rate Limits - -| Faucet Type | Cooldown Period | Drip Amount | Wallet Cap | Global Cap | -| --------------- | --------------- | ----------- | ---------- | ------------ | -| `CalibnetFIL` | 60 seconds | 1 tFIL | 2 tFIL | 200 tFIL | -| `CalibnetUSDFC` | 60 seconds | 5 tUSDFC | 10 tUSDFC | 1,000 tUSDFC | - -**Note:** All limits reset every 24 hours. Abuse, farming, or automated requests -are prohibited and may result in stricter limits or bans. - ---- - ## Status Codes | Status Code | Description | @@ -56,6 +39,8 @@ are prohibited and may result in stricter limits or bans. ### Success +#### Success claim for `CalibnetFIL` + - **Status:** `200 OK` - **Content:** Plain text string containing the transaction hash. @@ -71,6 +56,21 @@ curl "https://forest-explorer.chainsafe.dev/api/claim_token?faucet_info=Calibnet 0x06784dd239f7f0e01baa19a82877e17b7fcd6e1dd725913fd6f741a2a6c56ce5 ``` +#### Success claim for `CalibnetUSDFC` + +- **Status:** `200 OK` +- **Content:** Plain text string containing the transaction hash. + +```bash +curl "https://forest-explorer.chainsafe.dev/api/claim_token?faucet_info=CalibnetUSDFC&address=0xae9c4b9508c929966ef37209b336e5796d632cdc" +``` + +**Response:** + +```bash +0x8d75e2394dcf829ab9353370069b6d6afb04c88ea38c765ab4443a1587e12922 +``` + ### Failure #### 400 Bad Request @@ -143,6 +143,167 @@ ServerError|I'm a teapot - mainnet tokens are not available. --- +# Claim Token All API + +**Base URL:** `https://forest-explorer.chainsafe.dev` +**Endpoint:** `/api/claim_token_all` +**HTTP Method:** `GET` + +## Description + +Requests claims for both `CalibnetUSDFC` and `CalibnetFIL` in one call. Returns +a JSON array of per-claim results. Each item corresponds to one faucet claim. + +--- + +## Query Parameters + +| Parameter | Type | Required | Description | +| --------- | ------ | -------- | --------------------------------------------- | +| `address` | string | Yes | The wallet address to receive all the tokens. | + +--- + +## Status Codes + +| Status Code | Description | +| ----------- | -------------------------------- | +| 200 | Tokens successfully claimed | +| 400 | Bad request - invalid address | +| 429 | Too many requests - rate limited | +| 500 | Server error | + +--- + +### Claim Response Success & Failure + +The API returns a **JSON array**, where each object corresponds to a faucet +claim attempt. Each object contains: + +- `faucet_info`: A string identifying the faucet (e.g., `CalibnetFIL`, + `CalibnetUSDFC`) + +And either: + +- `tx_hash`: A string containing the transaction hash **if the claim was + successful**, + **or** +- `error`: An object containing the error details **if the claim failed** + +--- + +## Examples + +### Success + +- **Status:** `200 OK` + +**Example:** + +```bash +curl "https://forest-explorer.chainsafe.dev/api/claim_token_all?address=0xAe9C4b9508c929966ef37209b336E5796D632CDc" +``` + +**Response:** + +```json +[ + { + "faucet_info": "CalibnetUSDFC", + "tx_hash": "0x8d75e2394dcf829ab9353370069b6d6afb04c88ea38c765ab4443a1587e12922" + }, + { + "faucet_info": "CalibnetFIL", + "tx_hash": "0xf133c6aae45e40a48b71449229cb45f5ab5f2e7bd8ae488d1142319191ca8eb0" + } +] +``` + +### Failure + +#### 400 Bad Request + +- **Status:** `400 Bad Request` +- **Content:** JSON array where each item represents a faucet claim result. Each + item includes `faucet_info` and either a `tx_hash` (on success) or an `error` + object (on failure). + +**Example:** + +```bash +curl "https://forest-explorer.chainsafe.dev/api/claim_token_all?address=invalidaddress" +``` + +**Response:** + +```json +[ + { + "faucet_info": "CalibnetUSDFC", + "error": { + "ServerError": "Invalid address: Not a valid Testnet address" + } + }, + { + "faucet_info": "CalibnetFIL", + "error": { + "ServerError": "Invalid address: Not a valid Testnet address" + } + } +] +``` + +#### 429 Too Many Requests + +- **Status:** `429 Too Many Requests` +- **Content:** JSON array where each item represents a faucet claim result. Each + item includes `faucet_info` and either a `tx_hash` (on success) or an `error` + object (on failure). + +**Example:** + +```bash +curl "https://forest-explorer.chainsafe.dev/api/claim_token_all?address=0xAe9C4b9508c929966ef37209b336E5796D632CDc" +``` + +**Response:** + +```json +[ + { + "faucet_info": "CalibnetUSDFC", + "error": { + "ServerError": "Too many requests: Rate limited. Try again in 46 seconds." + } + }, + { + "faucet_info": "CalibnetFIL", + "error": { + "ServerError": "Too many requests: Rate limited. Try again in 12 seconds." + } + } +] +``` + +--- + +**Key points:** + +- Each address is subject to rate limiting to prevent abuse. +- This API only distributes Calibnet `tFIL` and `tUSDFC` tokens. + +## Rate Limits + +| Faucet Type | Cooldown Period | Drip Amount | Wallet Cap | Global Cap | +| --------------- | --------------- | ----------- | ---------- | ------------ | +| `CalibnetFIL` | 60 seconds | 1 tFIL | 2 tFIL | 200 tFIL | +| `CalibnetUSDFC` | 60 seconds | 5 tUSDFC | 10 tUSDFC | 1,000 tUSDFC | + +**Note:** All limits reset every 24 hours. Abuse, farming, or automated requests +are prohibited and may result in stricter limits or bans. + +--- + ## Faucet Top-Up Requests If you encounter a server error indicating that faucet is exhausted. diff --git a/src/faucet/server_api.rs b/src/faucet/server_api.rs index 8250b4d1..c13bc45f 100644 --- a/src/faucet/server_api.rs +++ b/src/faucet/server_api.rs @@ -5,10 +5,12 @@ use crate::utils::{ address::AnyAddress, lotus_json::{LotusJson, signed_message::SignedMessage}, }; +use alloy::primitives::TxHash; use anyhow::Result; use fvm_shared::address::Address; use fvm_shared::econ::TokenAmount; use leptos::{prelude::ServerFnError, server, server_fn::codec::GetUrl}; +use serde::{Deserialize, Serialize}; #[cfg(feature = "ssr")] use axum::http::StatusCode; @@ -196,6 +198,15 @@ pub async fn signed_erc20_transfer( Ok(signed) } +#[derive(Serialize, Deserialize)] +pub struct ClaimResponse { + pub faucet_info: FaucetInfo, + #[serde(skip_serializing_if = "Option::is_none")] + pub tx_hash: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + /// Server API endpoint for claiming calibnet tokens from the faucet. /// Returns a transaction ID on successful token claim. /// Supports distribution of `CalibnetFIL` and `CalibnetUSDFC` tokens. @@ -204,7 +215,7 @@ pub async fn signed_erc20_transfer( pub async fn claim_token( faucet_info: FaucetInfo, address: String, -) -> Result { +) -> Result { use crate::utils::rpc_context::Provider; use fvm_shared::address::set_current_network; use send_wrapper::SendWrapper; @@ -236,6 +247,30 @@ pub async fn claim_token( .await } +#[server(endpoint = "claim_token_all", input = GetUrl)] +pub async fn claim_token_all(address: String) -> Result, ServerFnError> { + let faucets = [FaucetInfo::CalibnetUSDFC, FaucetInfo::CalibnetFIL]; + let mut results = Vec::with_capacity(faucets.len()); + + for faucet in faucets { + let response = match claim_token(faucet, address.clone()).await { + Ok(tx_hash) => ClaimResponse { + faucet_info: faucet, + tx_hash: Some(tx_hash), + error: None, + }, + Err(e) => ClaimResponse { + faucet_info: faucet, + tx_hash: None, + error: Some(e), + }, + }; + results.push(response); + } + + Ok(results) +} + #[cfg(feature = "ssr")] fn parse_and_validate_address( address: &str, @@ -279,7 +314,7 @@ async fn handle_native_claim( recipient: Address, from: Address, rpc: crate::utils::rpc_context::Provider, -) -> Result { +) -> Result { use crate::utils::message::message_transfer; let id_address = rpc.lookup_id(recipient).await.unwrap_or_else(|_| { @@ -313,7 +348,7 @@ async fn handle_native_claim( .eth_get_transaction_hash_by_cid(cid) .await .map_err(ServerFnError::new)?; - Ok(tx_hash.to_string()) + Ok(tx_hash) } Err(err) => Err(handle_faucet_error(err)), } @@ -325,7 +360,7 @@ async fn handle_erc20_claim( recipient: Address, from: Address, rpc: crate::utils::rpc_context::Provider, -) -> Result { +) -> Result { use crate::utils::address::AddressAlloyExt; let eth_to = recipient.into_eth_address().map_err(ServerFnError::new)?; @@ -341,7 +376,7 @@ async fn handle_erc20_claim( .send_eth_transaction_signed(&signed) .await .map_err(ServerFnError::new)?; - Ok(tx_hash.to_string()) + Ok(tx_hash) } Err(err) => Err(handle_faucet_error(err)), } diff --git a/src/lib.rs b/src/lib.rs index be09a5c9..25e1606f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,6 +75,7 @@ mod ssr_imports { 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/utils/address.rs b/src/utils/address.rs index 41326e7f..3e7ff11e 100644 --- a/src/utils/address.rs +++ b/src/utils/address.rs @@ -108,7 +108,7 @@ impl AddressAlloyExt for Address { error!( "Cannot convert address {self} to Ethereum address. Only ID and Delegated addresses are supported." ); - bail!("invalid address {self}"); + bail!("Invalid address {self}, Only ID and Delegated addresses are supported."); } } }