Skip to content

Commit 05d9aee

Browse files
Implement claim all calibnet token API (#334)
1 parent 532406e commit 05d9aee

File tree

4 files changed

+220
-23
lines changed

4 files changed

+220
-23
lines changed

docs/api-documentation.md

Lines changed: 178 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ interact with the network. Users provide a valid wallet address and specify the
1212
token type (`faucet_info`) they wish to receive. On success, the API returns a
1313
transaction hash confirming the token transfer.
1414

15-
**Key points:**
16-
17-
- Each address is subject to rate limiting to prevent abuse.
18-
- This API only distributes Calibnet `tFIL` and `tUSDFC` tokens.
19-
2015
---
2116

2217
## Query Parameters
@@ -28,18 +23,6 @@ transaction hash confirming the token transfer.
2823

2924
---
3025

31-
## Rate Limits
32-
33-
| Faucet Type | Cooldown Period | Drip Amount | Wallet Cap | Global Cap |
34-
| --------------- | --------------- | ----------- | ---------- | ------------ |
35-
| `CalibnetFIL` | 60 seconds | 1 tFIL | 2 tFIL | 200 tFIL |
36-
| `CalibnetUSDFC` | 60 seconds | 5 tUSDFC | 10 tUSDFC | 1,000 tUSDFC |
37-
38-
**Note:** All limits reset every 24 hours. Abuse, farming, or automated requests
39-
are prohibited and may result in stricter limits or bans.
40-
41-
---
42-
4326
## Status Codes
4427

4528
| Status Code | Description |
@@ -56,6 +39,8 @@ are prohibited and may result in stricter limits or bans.
5639

5740
### Success
5841

42+
#### Success claim for `CalibnetFIL`
43+
5944
- **Status:** `200 OK`
6045
- **Content:** Plain text string containing the transaction hash.
6146

@@ -71,6 +56,21 @@ curl "https://forest-explorer.chainsafe.dev/api/claim_token?faucet_info=Calibnet
7156
0x06784dd239f7f0e01baa19a82877e17b7fcd6e1dd725913fd6f741a2a6c56ce5
7257
```
7358

59+
#### Success claim for `CalibnetUSDFC`
60+
61+
- **Status:** `200 OK`
62+
- **Content:** Plain text string containing the transaction hash.
63+
64+
```bash
65+
curl "https://forest-explorer.chainsafe.dev/api/claim_token?faucet_info=CalibnetUSDFC&address=0xae9c4b9508c929966ef37209b336e5796d632cdc"
66+
```
67+
68+
**Response:**
69+
70+
```bash
71+
0x8d75e2394dcf829ab9353370069b6d6afb04c88ea38c765ab4443a1587e12922
72+
```
73+
7474
### Failure
7575

7676
#### 400 Bad Request
@@ -143,6 +143,167 @@ ServerError|I'm a teapot - mainnet tokens are not available.
143143
144144
---
145145
146+
# Claim Token All API
147+
148+
**Base URL:** `https://forest-explorer.chainsafe.dev`
149+
**Endpoint:** `/api/claim_token_all`
150+
**HTTP Method:** `GET`
151+
152+
## Description
153+
154+
Requests claims for both `CalibnetUSDFC` and `CalibnetFIL` in one call. Returns
155+
a JSON array of per-claim results. Each item corresponds to one faucet claim.
156+
157+
---
158+
159+
## Query Parameters
160+
161+
| Parameter | Type | Required | Description |
162+
| --------- | ------ | -------- | --------------------------------------------- |
163+
| `address` | string | Yes | The wallet address to receive all the tokens. |
164+
165+
---
166+
167+
## Status Codes
168+
169+
| Status Code | Description |
170+
| ----------- | -------------------------------- |
171+
| 200 | Tokens successfully claimed |
172+
| 400 | Bad request - invalid address |
173+
| 429 | Too many requests - rate limited |
174+
| 500 | Server error |
175+
176+
---
177+
178+
### Claim Response Success & Failure
179+
180+
The API returns a **JSON array**, where each object corresponds to a faucet
181+
claim attempt. Each object contains:
182+
183+
- `faucet_info`: A string identifying the faucet (e.g., `CalibnetFIL`,
184+
`CalibnetUSDFC`)
185+
186+
And either:
187+
188+
- `tx_hash`: A string containing the transaction hash **if the claim was
189+
successful**,
190+
**or**
191+
- `error`: An object containing the error details **if the claim failed**
192+
193+
---
194+
195+
## Examples
196+
197+
### Success
198+
199+
- **Status:** `200 OK`
200+
201+
**Example:**
202+
203+
```bash
204+
curl "https://forest-explorer.chainsafe.dev/api/claim_token_all?address=0xAe9C4b9508c929966ef37209b336E5796D632CDc"
205+
```
206+
207+
**Response:**
208+
209+
```json
210+
[
211+
{
212+
"faucet_info": "CalibnetUSDFC",
213+
"tx_hash": "0x8d75e2394dcf829ab9353370069b6d6afb04c88ea38c765ab4443a1587e12922"
214+
},
215+
{
216+
"faucet_info": "CalibnetFIL",
217+
"tx_hash": "0xf133c6aae45e40a48b71449229cb45f5ab5f2e7bd8ae488d1142319191ca8eb0"
218+
}
219+
]
220+
```
221+
222+
### Failure
223+
224+
#### 400 Bad Request
225+
226+
- **Status:** `400 Bad Request`
227+
- **Content:** JSON array where each item represents a faucet claim result. Each
228+
item includes `faucet_info` and either a `tx_hash` (on success) or an `error`
229+
object (on failure).
230+
231+
**Example:**
232+
233+
```bash
234+
curl "https://forest-explorer.chainsafe.dev/api/claim_token_all?address=invalidaddress"
235+
```
236+
237+
**Response:**
238+
239+
```json
240+
[
241+
{
242+
"faucet_info": "CalibnetUSDFC",
243+
"error": {
244+
"ServerError": "Invalid address: Not a valid Testnet address"
245+
}
246+
},
247+
{
248+
"faucet_info": "CalibnetFIL",
249+
"error": {
250+
"ServerError": "Invalid address: Not a valid Testnet address"
251+
}
252+
}
253+
]
254+
```
255+
256+
#### 429 Too Many Requests
257+
258+
- **Status:** `429 Too Many Requests`
259+
- **Content:** JSON array where each item represents a faucet claim result. Each
260+
item includes `faucet_info` and either a `tx_hash` (on success) or an `error`
261+
object (on failure).
262+
263+
**Example:**
264+
265+
```bash
266+
curl "https://forest-explorer.chainsafe.dev/api/claim_token_all?address=0xAe9C4b9508c929966ef37209b336E5796D632CDc"
267+
```
268+
269+
**Response:**
270+
271+
```json
272+
[
273+
{
274+
"faucet_info": "CalibnetUSDFC",
275+
"error": {
276+
"ServerError": "Too many requests: Rate limited. Try again in 46 seconds."
277+
}
278+
},
279+
{
280+
"faucet_info": "CalibnetFIL",
281+
"error": {
282+
"ServerError": "Too many requests: Rate limited. Try again in 12 seconds."
283+
}
284+
}
285+
]
286+
```
287+
288+
---
289+
290+
**Key points:**
291+
292+
- Each address is subject to rate limiting to prevent abuse.
293+
- This API only distributes Calibnet `tFIL` and `tUSDFC` tokens.
294+
295+
## Rate Limits
296+
297+
| Faucet Type | Cooldown Period | Drip Amount | Wallet Cap | Global Cap |
298+
| --------------- | --------------- | ----------- | ---------- | ------------ |
299+
| `CalibnetFIL` | 60 seconds | 1 tFIL | 2 tFIL | 200 tFIL |
300+
| `CalibnetUSDFC` | 60 seconds | 5 tUSDFC | 10 tUSDFC | 1,000 tUSDFC |
301+
302+
**Note:** All limits reset every 24 hours. Abuse, farming, or automated requests
303+
are prohibited and may result in stricter limits or bans.
304+
305+
---
306+
146307
## Faucet Top-Up Requests
147308
148309
If you encounter a server error indicating that faucet is exhausted.

src/faucet/server_api.rs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ use crate::utils::{
55
address::AnyAddress,
66
lotus_json::{LotusJson, signed_message::SignedMessage},
77
};
8+
use alloy::primitives::TxHash;
89
use anyhow::Result;
910
use fvm_shared::address::Address;
1011
use fvm_shared::econ::TokenAmount;
1112
use leptos::{prelude::ServerFnError, server, server_fn::codec::GetUrl};
13+
use serde::{Deserialize, Serialize};
1214

1315
#[cfg(feature = "ssr")]
1416
use axum::http::StatusCode;
@@ -196,6 +198,15 @@ pub async fn signed_erc20_transfer(
196198
Ok(signed)
197199
}
198200

201+
#[derive(Serialize, Deserialize)]
202+
pub struct ClaimResponse {
203+
pub faucet_info: FaucetInfo,
204+
#[serde(skip_serializing_if = "Option::is_none")]
205+
pub tx_hash: Option<TxHash>,
206+
#[serde(skip_serializing_if = "Option::is_none")]
207+
pub error: Option<ServerFnError>,
208+
}
209+
199210
/// Server API endpoint for claiming calibnet tokens from the faucet.
200211
/// Returns a transaction ID on successful token claim.
201212
/// Supports distribution of `CalibnetFIL` and `CalibnetUSDFC` tokens.
@@ -204,7 +215,7 @@ pub async fn signed_erc20_transfer(
204215
pub async fn claim_token(
205216
faucet_info: FaucetInfo,
206217
address: String,
207-
) -> Result<String, ServerFnError> {
218+
) -> Result<TxHash, ServerFnError> {
208219
use crate::utils::rpc_context::Provider;
209220
use fvm_shared::address::set_current_network;
210221
use send_wrapper::SendWrapper;
@@ -236,6 +247,30 @@ pub async fn claim_token(
236247
.await
237248
}
238249

250+
#[server(endpoint = "claim_token_all", input = GetUrl)]
251+
pub async fn claim_token_all(address: String) -> Result<Vec<ClaimResponse>, ServerFnError> {
252+
let faucets = [FaucetInfo::CalibnetUSDFC, FaucetInfo::CalibnetFIL];
253+
let mut results = Vec::with_capacity(faucets.len());
254+
255+
for faucet in faucets {
256+
let response = match claim_token(faucet, address.clone()).await {
257+
Ok(tx_hash) => ClaimResponse {
258+
faucet_info: faucet,
259+
tx_hash: Some(tx_hash),
260+
error: None,
261+
},
262+
Err(e) => ClaimResponse {
263+
faucet_info: faucet,
264+
tx_hash: None,
265+
error: Some(e),
266+
},
267+
};
268+
results.push(response);
269+
}
270+
271+
Ok(results)
272+
}
273+
239274
#[cfg(feature = "ssr")]
240275
fn parse_and_validate_address(
241276
address: &str,
@@ -279,7 +314,7 @@ async fn handle_native_claim(
279314
recipient: Address,
280315
from: Address,
281316
rpc: crate::utils::rpc_context::Provider,
282-
) -> Result<String, ServerFnError> {
317+
) -> Result<TxHash, ServerFnError> {
283318
use crate::utils::message::message_transfer;
284319

285320
let id_address = rpc.lookup_id(recipient).await.unwrap_or_else(|_| {
@@ -313,7 +348,7 @@ async fn handle_native_claim(
313348
.eth_get_transaction_hash_by_cid(cid)
314349
.await
315350
.map_err(ServerFnError::new)?;
316-
Ok(tx_hash.to_string())
351+
Ok(tx_hash)
317352
}
318353
Err(err) => Err(handle_faucet_error(err)),
319354
}
@@ -325,7 +360,7 @@ async fn handle_erc20_claim(
325360
recipient: Address,
326361
from: Address,
327362
rpc: crate::utils::rpc_context::Provider,
328-
) -> Result<String, ServerFnError> {
363+
) -> Result<TxHash, ServerFnError> {
329364
use crate::utils::address::AddressAlloyExt;
330365

331366
let eth_to = recipient.into_eth_address().map_err(ServerFnError::new)?;
@@ -341,7 +376,7 @@ async fn handle_erc20_claim(
341376
.send_eth_transaction_signed(&signed)
342377
.await
343378
.map_err(ServerFnError::new)?;
344-
Ok(tx_hash.to_string())
379+
Ok(tx_hash)
345380
}
346381
Err(err) => Err(handle_faucet_error(err)),
347382
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ mod ssr_imports {
7575
server_fn::axum::register_explicit::<faucet::server_api::SignedErc20Transfer>();
7676
server_fn::axum::register_explicit::<faucet::server_api::FaucetAddress>();
7777
server_fn::axum::register_explicit::<faucet::server_api::ClaimToken>();
78+
server_fn::axum::register_explicit::<faucet::server_api::ClaimTokenAll>();
7879
}
7980

8081
#[event(fetch)]

src/utils/address.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ impl AddressAlloyExt for Address {
108108
error!(
109109
"Cannot convert address {self} to Ethereum address. Only ID and Delegated addresses are supported."
110110
);
111-
bail!("invalid address {self}");
111+
bail!("Invalid address {self}, Only ID and Delegated addresses are supported.");
112112
}
113113
}
114114
}

0 commit comments

Comments
 (0)