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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ target/
.idea/
.vscode
.worktrees
.DS_Store
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Root guidance applies to the whole workspace. Also read the crate-local
public export endpoints.
- `stonks_api_client` (`crates/stonks/`): REST wrapper for Stonks public-token
metadata and Virtual Pool address discovery.
- `dedust_api_client` (`crates/dedust/`): REST wrapper for DeDust API v2.
- `dedust_api_client` (`crates/dedust/`): REST wrapper for the DeDust asset registry, API v4 pool registries, and legacy API v2 operations.
- `swap_coffee_api_client` (`crates/swap_coffee/`): REST wrapper for Swap Coffee
API v1.
- `tonco_api_client` (`crates/tonco_api_client/`): GraphQL wrapper for Tonco
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ MRs are welcome.
|-----------------------|----------------------------------------------------------|-------------|--------------|
| https://ston.fi | [stonfi_api_client](crates/stonfi) | Supported | STON.fi API v1 assets, pools, farms, routers, swap/liquidity simulation, wallet views, stats, transactions, and public export feeds. |
| https://app.stonks.cash | [stonks_api_client](crates/stonks) | Supported | Stonks public-token tax metadata and paginated Virtual Pool address discovery. |
| https://dedust.io | [dedust_api_client](crates/dedust) | Supported | DeDust API v2 assets, pools, pool trades, and routing plans. |
| https://dedust.io | [dedust_api_client](crates/dedust) | Supported | DeDust asset metadata, v4 Classic/Stable/CPMM v2/Uranus pool registries, and legacy v2 operations. |
| https://app.tonco.io/ | [tonco_api_client](crates/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](crates/swap_coffee) | Supported | Swap Coffee API v1 tokens and pools. |
| Bidask | [bidask_api_client](crates/bidask) | Unsupported | Legacy source only; not recommended for application integration and not published. |
Expand Down
67 changes: 44 additions & 23 deletions crates/dedust/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

## Scope

This crate is `dedust_api_client`, a Rust library crate that wraps DeDust REST
API v2.
This crate is `dedust_api_client`, a public Rust library crate that wraps the
DeDust asset registry, API v4 pool registries, and legacy REST API v2.

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 @@ -14,65 +14,86 @@ changes.
The crate exposes a thin typed client:

- `DedustApiClient::builder().build()?`
- `client.assets.exec(&AssetsRequest::...)`
- `client.v2.exec(&V2Request::...)`
- request params in `v2/request.rs`
- response enums and models in `v2/response.rs` and `v2/types.rs`
- `client.v4.exec(&V4Request::...)`
- raw request, response, and model modules under `assets/`, `v2/`, and `v4/`

Keep DeDust-specific address formatting and endpoint mapping in this crate.
Do not join asset metadata into pool registries or derive dynamic pool state.

## Public API Boundary

Treat these as public contracts:

- `DedustApiClient`
- `DEFAULT_ASSETS_URL`
- `DEFAULT_API_V2_URL`
- `DEFAULT_API_V4_URL`
- `AssetsApiClient`, `AssetsRequest`, `AssetsResponse`, and asset wire models
- `V2ApiClient`
- `V2Request`
- `RoutingPlanParams`
- `V2Response` and public response/type structs
- `V4ApiClient`, `V4Request`, `V4Response`, and pool-registry wire models
- `unwrap_response!`
- `unwrap_assets_response!`
- `unwrap_v4_response!`

Request parameter and response/model POD structs are `#[non_exhaustive]`; use
`Default::default().with_<field>(...)` or request parameter constructors instead
of struct literals in downstream examples and integration tests. Pass request
parameters directly to `client.v2.exec` where `Into<V2Request>` is implemented.
Public enums are `#[non_exhaustive]`; downstream matches need wildcard arms.
of struct literals in downstream examples and integration tests. Public enums
are `#[non_exhaustive]`; downstream matches need wildcard arms.

The existing `with_api_url` and `with_executor` builder setters configure v2.
Use `with_assets_url`/`with_assets_executor` and
`with_v4_url`/`with_v4_executor` for the other origins.

`RoutingPlanParams::new` maps the zero TON address to `native` and all other
addresses to `jetton:<address>`. Do not change that mapping without validating
DeDust API expectations and updating examples.

## Live API Notes

Tests in `tests/test_api_v2.rs` hit the live DeDust API. Prefer assertions that
prove endpoint support and response parsing without relying on volatile pool
counts, routing amounts, or ordering.
Tests in `tests/test_assets.rs`, `tests/test_api_v2.rs`, and
`tests/test_api_v4.rs` hit the live DeDust services. Prefer assertions that
prove endpoint support and response parsing without relying on volatile asset
or pool counts, routing amounts, or ordering.

The asset registry uses friendly TON addresses and absolute image URLs. The v4
pool registries use raw `workchain:hex_hash` addresses and asset identifiers
such as `native` and `jetton:0:<hash>`. They provide discovery/configuration
records, not v2 dynamic fields such as reserves, supply, price, volume, or fees.
Keep legacy v2 asset and pool operations available for backward compatibility,
but do not recommend them for new registry integrations.

## Downstream Integration Example

```rust
use dedust_api_client::api_client::DedustApiClient;
use dedust_api_client::v2::{RoutingPlanParams, V2Request, V2Response};
use dedust_api_client::assets::{AssetsRequest, AssetsResponse};
use dedust_api_client::v4::{V4Request, V4Response};

# async fn example() -> anyhow::Result<()> {
let client = DedustApiClient::builder().build()?;
let params = RoutingPlanParams::new(
"0:0000000000000000000000000000000000000000000000000000000000000000",
"0:0000000000000000000000000000000000000000000000000000000000000000",
"1000000000",
);
let response = client.v2.exec(params).await?;

match response {
V2Response::RoutingPlan(routes) => println!("routes: {}", routes.len()),
other => anyhow::bail!("unexpected DeDust response: {other:?}"),
let assets_response = client.assets.exec(AssetsRequest::List).await?;
let pools_response = client.v4.exec(V4Request::AllCpmmPools).await?;

match assets_response {
AssetsResponse::List(assets) => println!("assets: {}", assets.len()),
other => anyhow::bail!("unexpected DeDust asset response: {other:?}"),
}
match pools_response {
V4Response::AllCpmmPools(pools) => println!("CPMM v2 pools: {}", pools.len()),
other => anyhow::bail!("unexpected DeDust v4 response: {other:?}"),
}
# Ok(())
# }
```

Final applications should keep address, amount, and slippage interpretation in
their own domain layer.
Final applications should keep address conversion, pool hydration, amount and
slippage interpretation, persistence, and fallback behavior in their own
domain layer.

## Validation

Expand Down
1 change: 1 addition & 0 deletions crates/dedust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ derive_more.workspace = true
tokio.workspace = true
env_logger.workspace = true
anyhow.workspace = true
serde_json.workspace = true
94 changes: 67 additions & 27 deletions crates/dedust/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# dedust_api_client

Thin typed wrapper for the [DeDust API v2](https://api.dedust.io/).
Thin typed wrapper for the [DeDust](https://dedust.io/) asset registry, API v4
pool registries, and legacy API v2.

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.
Use this crate when an application needs raw typed access to DeDust asset
metadata, pool discovery/configuration, pool trades, or routing plans. The crate
does not join asset metadata into pools, load on-chain pool state, choose routes,
calculate slippage, execute swaps, or normalize DeDust data into a shared DEX
domain model.

## Usage

Expand All @@ -16,35 +19,65 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

The minimum supported Rust version (MSRV) is 1.88.

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.
Run requests inside an async Tokio runtime. Match response enums with a wildcard
arm because they are non-exhaustive.

```rust,no_run
use dedust_api_client::api_client::DedustApiClient;
use dedust_api_client::v2::{RoutingPlanParams, V2Response};
use dedust_api_client::assets::{AssetsRequest, AssetsResponse};
use dedust_api_client::v4::{V4Request, V4Response};

# 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.v2.exec(params).await?;

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

let assets_response = client.assets.exec(AssetsRequest::List).await?;
match assets_response {
AssetsResponse::List(assets) => println!("assets: {}", assets.len()),
_ => println!("unexpected DeDust asset response variant"),
}

let pools_response = client.v4.exec(V4Request::AllCpmmPools).await?;
match pools_response {
V4Response::AllCpmmPools(pools) => println!("CPMM v2 pools: {}", pools.len()),
_ => println!("unexpected DeDust v4 response variant"),
}
# Ok(())
# }
```

`RoutingPlanParams::new` maps the zero TON address to `native` and all other
addresses to `jetton:<address>`.
The asset registry returns friendly TON addresses and absolute image URLs. The
v4 pool registries return raw `workchain:hex_hash` addresses and asset strings
such as `native` and `jetton:0:<hash>`. Applications own any address conversion,
metadata joins, and dynamic pool-state hydration.

## Asset Registry

| Method | Endpoint | Request | Response |
|--------|------------|-----------------------|-----------------------|
| GET | /list.json | `AssetsRequest::List` | `AssetsResponse::List` |

The default asset-registry origin is `https://assets.dedust.io`.

## Supported Endpoints
## API v4 Pool Registries

| Method | Endpoint | Request | Response |
|--------|---------------------------|------------------------------------|------------------------------------|
| GET | /get_pools_allclassic | `V4Request::AllClassicPools` | `V4Response::AllClassicPools` |
| GET | /get_pools_allstable | `V4Request::AllStablePools` | `V4Response::AllStablePools` |
| GET | /get_pools_allcpmm | `V4Request::AllCpmmPools` | `V4Response::AllCpmmPools` |
| GET | /get_pools_alluranus | `V4Request::AllUranusPools` | `V4Response::AllUranusPools` |

The default v4 origin is `https://mainnet.api.dedust.io/v4/api`. These endpoints
return discovery and configuration records. They do not include v2 fields such
as reserves, liquidity-token supply, logical time, last price, volume, or
accumulated fees. Uranus records identify launchpad tokens and fundraising
configuration rather than ordinary two-asset pools.

## Legacy API v2

The following operations remain available through `client.v2` for backward
compatibility. The v2 asset and pool-list endpoints are considered legacy and
should not be selected for new registry integrations.

| Method | Supported |
|------------------------------------------|-----------|
Expand All @@ -69,11 +102,18 @@ addresses to `jetton:<address>`.
| /v2/prices | |
| /v2/routing/plan | ✅ |

Public request and response types are marked `#[non_exhaustive]` for semver
`RoutingPlanParams::new` maps the zero TON address to `native` and all other
addresses to `jetton:<address>`.

Public request and response types are marked `#[non_exhaustive]` for SemVer
headroom. Build public POD structs with `Default::default().with_<field>(...)`
or request parameter constructors, pass request parameters directly to
`client.v2.exec` where `Into<V2Request>` is implemented, and include a wildcard
arm when matching response enums.
or request parameter constructors, pass request parameters directly where
`Into<Request>` is implemented, and include a wildcard arm when matching public
enums.

The existing `with_api_url` and `with_executor` builder setters configure v2.
Use `with_assets_url`/`with_assets_executor` and
`with_v4_url`/`with_v4_executor` to override the other origins independently.

Live API tests hit DeDust directly. Pool counts, routing amounts, and ordering
can drift with upstream state.
Live API tests hit DeDust directly. Asset and pool counts, metadata, routing
amounts, and ordering can drift with upstream state.
16 changes: 14 additions & 2 deletions crates/dedust/src/api_client.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
mod builder;

use crate::api_client::builder::Builder;
use crate::assets::AssetsApiClient;
use crate::v2::V2ApiClient;
use crate::v4::V4ApiClient;

/// Default base URL for the DeDust asset registry.
pub const DEFAULT_ASSETS_URL: &str = "https://assets.dedust.io";

/// Default base URL for DeDust API v2.
pub const DEFAULT_API_V2_URL: &str = "https://api.dedust.io/v2";

/// DeDust service client with a child client for API v2.
/// Default base URL for the DeDust API v4 pool registry.
pub const DEFAULT_API_V4_URL: &str = "https://mainnet.api.dedust.io/v4/api";

/// DeDust service client with child clients for each upstream API surface.
#[derive(Clone)]
#[non_exhaustive]
pub struct DedustApiClient {
/// DeDust asset-registry execution client.
pub assets: AssetsApiClient,
/// DeDust API v2 execution client.
pub v2: V2ApiClient,
/// DeDust API v4 pool-registry execution client.
pub v4: V4ApiClient,
}

impl DedustApiClient {
/// Start configuring a DeDust client with the default v2 endpoint.
/// Start configuring a DeDust client with the default asset, v2, and v4 endpoints.
pub fn builder() -> Builder { Builder::new() }
}
26 changes: 23 additions & 3 deletions crates/dedust/src/api_client/builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::api_client::{DedustApiClient, DEFAULT_API_V2_URL};
use crate::api_client::{DedustApiClient, DEFAULT_API_V2_URL, DEFAULT_API_V4_URL, DEFAULT_ASSETS_URL};
use crate::assets::AssetsApiClient;
use crate::v2::V2ApiClient;
use crate::v4::V4ApiClient;
use api_clients_core::{ApiClientsResult, Executor};
use derive_setters::Setters;
use std::sync::Arc;
Expand All @@ -10,29 +12,47 @@ use std::sync::Arc;
#[non_exhaustive]
pub struct Builder {
api_url: String,
assets_url: String,
v4_url: String,
executor: Option<Arc<Executor>>,
assets_executor: Option<Arc<Executor>>,
v4_executor: Option<Arc<Executor>>,
}

impl Builder {
pub(super) fn new() -> Self {
Self {
api_url: DEFAULT_API_V2_URL.to_string(),
assets_url: DEFAULT_ASSETS_URL.to_string(),
v4_url: DEFAULT_API_V4_URL.to_string(),
executor: None,
assets_executor: None,
v4_executor: None,
}
}

/// Build the configured DeDust client.
///
/// # Errors
///
/// Returns an error if the shared executor cannot be constructed.
/// Returns an error if any shared executor cannot be constructed.
pub fn build(self) -> ApiClientsResult<DedustApiClient> {
let executor = match self.executor {
Some(executor) => executor,
None => Executor::builder(self.api_url).build()?.into(),
};
let assets_executor = match self.assets_executor {
Some(executor) => executor,
None => Executor::builder(self.assets_url).build()?.into(),
};
let v4_executor = match self.v4_executor {
Some(executor) => executor,
None => Executor::builder(self.v4_url).build()?.into(),
};

let assets = AssetsApiClient::new(assets_executor);
let v2 = V2ApiClient::new(executor);
Ok(DedustApiClient { v2 })
let v4 = V4ApiClient::new(v4_executor);
Ok(DedustApiClient { assets, v2, v4 })
}
}
Loading
Loading