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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Root guidance applies to the whole workspace. Also read the crate-local

- `api_clients_core` (`core/`): shared HTTP executor, retry setup, and common
error/result types.
- `stonfi_api_client`: REST wrapper for STON.fi API v1.
- `stonfi_api_client`: REST wrapper for STON.fi API v1 and public export endpoints.
- `dedust_api_client`: REST wrapper for DeDust API v2.
- `swap_coffee_api_client`: REST wrapper for Swap Coffee API v1.
- `tonco_api_client`: GraphQL wrapper for Tonco Indexer.
Expand Down
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
# api_clients_rs
Rust bindings for different services. MRs are welcome!

| Service | Client | Status |
|------------------------|----------------------------------------------------|-------------|
| https://ston.fi | [stonfi_api_client](stonfi_api_client) | Supported |
| https://dedust.io | [dedust_api_client](dedust_api_client) | Supported |
| https://app.tonco.io/ | [tonco_api_client](tonco_api_client) | Supported |
| https://swap.coffee | [swap_coffee_api_client](swap_coffee_api_client) | Supported |
| Bidask | [bidask_api_client](bidask_api_client) | Unsupported |
Thin Rust API-client wrappers for TON ecosystem services. Each crate stays close
to its upstream API contract and exposes typed request/response models without
adding application-specific swap, routing, persistence, or fallback logic.

MRs are welcome.

| Service | Client | Status | Capabilities |
|------------------------|----------------------------------------------------|-------------|--------------|
| https://ston.fi | [stonfi_api_client](stonfi_api_client) | Supported | STON.fi API v1 assets, pools, farms, routers, swap/liquidity simulation, wallet views, stats, transactions, and public export feeds. |
| https://dedust.io | [dedust_api_client](dedust_api_client) | Supported | DeDust API v2 assets, pools, pool trades, and routing plans. |
| https://app.tonco.io/ | [tonco_api_client](tonco_api_client) | Supported | Low-level Tonco Indexer GraphQL execution with caller-owned query/schema files and generated types. |
| https://swap.coffee | [swap_coffee_api_client](swap_coffee_api_client) | Supported | Swap Coffee API v1 tokens and pools. |
| Bidask | [bidask_api_client](bidask_api_client) | Unsupported | Legacy source only; not recommended for application integration and not published. |


If you're interested in some particular endpoint which is not implemented yet, just raise an issue and I'll add it.
Expand All @@ -19,6 +24,7 @@ Add the crate for the service you need:
```toml
[dependencies]
stonfi_api_client = "0.8"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

Then build a client and execute the typed request in an async Tokio runtime:
Expand Down
5 changes: 5 additions & 0 deletions bidask_api_client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ is currently documented as supported by this workspace.
Public request and response types are marked `#[non_exhaustive]` for semver
headroom. Public POD structs support `Default::default().with_<field>(...)` for
tests and migration code. Include a wildcard arm when matching response enums.

The source still contains legacy request/response wrappers for migration and
reference work, but they are not part of the supported public-library guidance.
Future work should not add examples or recommend runtime integration unless
Bidask support is explicitly revived.
2 changes: 2 additions & 0 deletions bidask_api_client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![doc = include_str!("../README.md")]

pub use api_clients_core;
pub mod api;
pub mod api_client;
1 change: 1 addition & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "api_clients_core"
version = "0.2.1"
description = "core API clients features for various services"
readme = "README.md"
edition.workspace = true
license.workspace = true
repository.workspace = true
Expand Down
77 changes: 77 additions & 0 deletions core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# api_clients_core

Shared transport and error primitives for the API client crates in this
workspace.

Most applications should depend on one of the service crates, such as
`stonfi_api_client` or `dedust_api_client`, instead of using this crate
directly. Use `api_clients_core` directly when you need to share a configured
HTTP executor across service clients, inject a custom `reqwest_middleware`
client, or build another thin service wrapper in this workspace.

## Capabilities

- `Executor` executes JSON HTTP requests for the service-specific clients.
- `Executor::builder(endpoint)` configures the base endpoint, retry count,
request timeout, max RPS, and optional middleware client.
- `exec_get`, `exec_get_extra`, `exec_post_qs`, and `exec_post_body` parse
successful JSON responses into caller-provided `DeserializeOwned` types.
- `ApiClientsError` classifies client errors, server errors, invalid arguments,
unexpected response bodies, and transport/internal failures.

The default executor retries transient failures 3 times, uses a 10-second
request timeout, and applies a smooth 10 RPS client-side rate limit.

## Usage

```toml
[dependencies]
api_clients_core = "0.2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

```rust,no_run
use api_clients_core::{ApiClientsError, Executor};
use std::time::Duration;

# async fn example() -> Result<(), ApiClientsError> {
let executor = Executor::builder("https://api.example.com")
.with_retry_count(3)
.with_timeout(Duration::from_secs(10))
.with_max_rps(10)
.build()?;

let response: String = executor.exec_get("health").await?;
println!("{response}");
# Ok(())
# }
```

## Error Handling

Handle errors at the application boundary and include a wildcard arm because
`ApiClientsError` is `#[non_exhaustive]`:

```rust
use api_clients_core::ApiClientsError;

fn classify_error(err: ApiClientsError) -> &'static str {
match err {
ApiClientsError::Client(_, _) => "client",
ApiClientsError::Server(_, _) => "server",
ApiClientsError::Network(_, _) => "network",
ApiClientsError::UnexpectedResponse(_) => "unexpected-response",
_ => "other",
}
}
```

## Endpoint Joining

`Executor` joins endpoint and path with `format!("{endpoint}/{path}")`. Endpoint
strings normally should not end with `/`; otherwise requests can accidentally
target paths with `//`.

`exec_get` and `exec_get_extra` serialize query strings with `serde_qs`.
`exec_post_qs` sends POST requests with query-string parameters.
`exec_post_body` serializes the request body as JSON.
2 changes: 2 additions & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![doc = include_str!("../README.md")]

mod errors;
mod executor;

Expand Down
49 changes: 48 additions & 1 deletion dedust_api_client/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,48 @@
### https://api.dedust.io/
# dedust_api_client

Thin typed wrapper for the [DeDust API v2](https://api.dedust.io/).

Use this crate when an application needs typed access to DeDust assets, pools,
pool trades, or routing plans. The crate does not choose routes, calculate
slippage, execute swaps, or normalize DeDust data into a shared DEX domain model.

## Usage

```toml
[dependencies]
dedust_api_client = "0.6"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```

Run requests inside an async Tokio runtime. Pass request parameter structs
directly where `Into<V2Request>` is implemented, and match response enums with a
wildcard arm.

```rust,no_run
use dedust_api_client::api_client::DedustApiClient;
use dedust_api_client::api_v2::{RoutingPlanParams, V2Response};

# async fn example() -> Result<(), Box<dyn std::error::Error>> {
let client = DedustApiClient::builder().build()?;
let params = RoutingPlanParams::new(
"0:0000000000000000000000000000000000000000000000000000000000000000",
"0:0000000000000000000000000000000000000000000000000000000000000000",
"1000000000",
);
let response = client.exec_api_v2(params).await?;

match response {
V2Response::RoutingPlan(routes) => println!("route groups: {}", routes.len()),
_ => println!("unexpected DeDust response variant"),
}
# Ok(())
# }
```

`RoutingPlanParams::new` maps the zero TON address to `native` and all other
addresses to `jetton:<address>`.

## Supported Endpoints

| Method | Supported |
|------------------------------------------|-----------|
Expand Down Expand Up @@ -28,3 +72,6 @@ headroom. Build public POD structs with `Default::default().with_<field>(...)`
or request parameter constructors, pass request parameters directly to
`exec_api_v2` where `Into<V2Request>` is implemented, and include a wildcard arm
when matching response enums.

Live API tests hit DeDust directly. Pool counts, routing amounts, and ordering
can drift with upstream state.
2 changes: 2 additions & 0 deletions dedust_api_client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![doc = include_str!("../README.md")]

pub use api_clients_core; // re-export
pub mod api_client;
pub mod api_v2;
31 changes: 18 additions & 13 deletions dedust_api_client/tests/test_api_v2.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,62 @@
use anyhow::Result;
use api_clients_core::Executor;
use dedust_api_client::api_client::DedustApiClient;
use dedust_api_client::api_client::DEFAULT_API_V2_URL;
use dedust_api_client::api_v2::{RoutingPlanParams, V2Request};
use dedust_api_client::unwrap_response;
use std::sync::Arc;
use std::time::Duration;

fn init_env() -> DedustApiClient {
fn init_env() -> Result<DedustApiClient> {
let _ = env_logger::builder().filter_level(log::LevelFilter::Debug).try_init();
DedustApiClient::builder().build().unwrap()
let executor = Executor::builder(DEFAULT_API_V2_URL).with_timeout(Duration::from_secs(60)).build()?;
Ok(DedustApiClient::builder().with_executor(Arc::new(executor)).build()?)
}

#[tokio::test]
async fn test_assets() -> Result<()> {
let client = init_env();
let client = init_env()?;
let request = V2Request::Assets;
let response = unwrap_response!(Assets, client.exec_api_v2(&request).await?)?;
assert_ne!(response, vec![]);
assert!(!response.is_empty());
Ok(())
}

#[tokio::test]
async fn test_pools() -> Result<()> {
let client = init_env();
let client = init_env()?;
let request = V2Request::Pools;
let response = unwrap_response!(Pools, client.exec_api_v2(&request).await?)?;
assert_ne!(response, vec![]);
assert!(!response.is_empty());
Ok(())
}

#[tokio::test]
async fn test_pools_lite() -> Result<()> {
let client = init_env();
let client = init_env()?;
let request = V2Request::PoolsLite;
let response = unwrap_response!(PoolsLite, client.exec_api_v2(&request).await?)?;
assert_ne!(response, vec![]);
assert!(!response.is_empty());
Ok(())
}

#[tokio::test]
async fn test_pool_trades() -> Result<()> {
let client = init_env();
let client = init_env()?;
let request = V2Request::PoolTrades("EQAADLqcF3lNb1O_GQLowwky8vvUGXuzPRNGvKBwBxjsHR7s".to_string());
let response = unwrap_response!(PoolTrades, client.exec_api_v2(&request).await?)?;
assert_ne!(response, vec![]);
assert!(!response.is_empty());
Ok(())
}

#[tokio::test]
async fn test_routing_plan() -> Result<()> {
let client = init_env();
let client = init_env()?;
let usdt_addr = "0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe";
let ton_addr = "0:0000000000000000000000000000000000000000000000000000000000000000";
let params = RoutingPlanParams::new(usdt_addr, ton_addr, &10.to_string());
let response = unwrap_response!(RoutingPlan, client.exec_api_v2(params).await?)?;
assert_ne!(response.len(), 0);
assert_ne!(response[0], vec![]);
assert!(!response.is_empty());
assert!(!response[0].is_empty());
Ok(())
}
14 changes: 10 additions & 4 deletions stonfi_api_client/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Scope

This crate is `stonfi_api_client`, a Rust library crate that wraps STON.fi REST
API v1.
API v1 and public export endpoints.

Use the repository root `AGENTS.md` first, then this file. Use the
`rust-library-review` skill for public API, docs, package, or agent-guidance
Expand All @@ -15,9 +15,12 @@ The crate exposes a thin typed client:

- `StonfiApiClient::builder().build()?`
- `client.v1.exec(&V1Request::...)`
- `client.export.exec(&ExportRequest::...)`
- request params in `v1/request.rs`
- response enums and models in `v1/response.rs`, `v1/types.rs`, and
`v1/actions.rs`
- response enums and response wrappers in `v1/response.rs` and models/actions in
`v1/types.rs`
- export dispatch in `export.rs`, requests in `export/request.rs`, responses in
`export/response.rs`, and export models in `export/types.rs`

Do not add application-level swap orchestration or transaction execution logic
here. This crate only wraps STON.fi API responses.
Expand All @@ -27,11 +30,14 @@ here. This crate only wraps STON.fi API responses.
Treat these as public contracts:

- `StonfiApiClient`
- `DEFAULT_API_URL`
- `DEFAULT_API_V1_URL`
- `V1ApiClient`
- `V1Request`
- `ExportApiClient`
- `ExportRequest`
- all public request parameter structs
- `V1Response` and public response/action/type structs
- `V1Response`, `ExportResponse`, and public response/action/type structs
- `unwrap_response!`

Request parameter and response/model POD structs are `#[non_exhaustive]`; use
Expand Down
Loading
Loading