Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 178 additions & 17 deletions docs/api-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 |
Expand All @@ -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.

Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down
45 changes: 40 additions & 5 deletions src/faucet/server_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<TxHash>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<ServerFnError>,
}

/// 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.
Expand All @@ -204,7 +215,7 @@ pub async fn signed_erc20_transfer(
pub async fn claim_token(
faucet_info: FaucetInfo,
address: String,
) -> Result<String, ServerFnError> {
) -> Result<TxHash, ServerFnError> {
use crate::utils::rpc_context::Provider;
use fvm_shared::address::set_current_network;
use send_wrapper::SendWrapper;
Expand Down Expand Up @@ -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<Vec<ClaimResponse>, 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,
Expand Down Expand Up @@ -279,7 +314,7 @@ async fn handle_native_claim(
recipient: Address,
from: Address,
rpc: crate::utils::rpc_context::Provider,
) -> Result<String, ServerFnError> {
) -> Result<TxHash, ServerFnError> {
use crate::utils::message::message_transfer;

let id_address = rpc.lookup_id(recipient).await.unwrap_or_else(|_| {
Expand Down Expand Up @@ -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)),
}
Expand All @@ -325,7 +360,7 @@ async fn handle_erc20_claim(
recipient: Address,
from: Address,
rpc: crate::utils::rpc_context::Provider,
) -> Result<String, ServerFnError> {
) -> Result<TxHash, ServerFnError> {
use crate::utils::address::AddressAlloyExt;

let eth_to = recipient.into_eth_address().map_err(ServerFnError::new)?;
Expand All @@ -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)),
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ mod ssr_imports {
server_fn::axum::register_explicit::<faucet::server_api::SignedErc20Transfer>();
server_fn::axum::register_explicit::<faucet::server_api::FaucetAddress>();
server_fn::axum::register_explicit::<faucet::server_api::ClaimToken>();
server_fn::axum::register_explicit::<faucet::server_api::ClaimTokenAll>();
}

#[event(fetch)]
Expand Down
2 changes: 1 addition & 1 deletion src/utils/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
}
}
Expand Down
Loading