diff --git a/AGENTS.md b/AGENTS.md index b129861..befb7b9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/README.md b/README.md index 5972780..30d244a 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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: diff --git a/bidask_api_client/README.md b/bidask_api_client/README.md index 181d46e..dee456e 100644 --- a/bidask_api_client/README.md +++ b/bidask_api_client/README.md @@ -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_(...)` 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. diff --git a/bidask_api_client/src/lib.rs b/bidask_api_client/src/lib.rs index f9b3479..5abd16f 100644 --- a/bidask_api_client/src/lib.rs +++ b/bidask_api_client/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + pub use api_clients_core; pub mod api; pub mod api_client; diff --git a/core/Cargo.toml b/core/Cargo.toml index 3024153..6608c33 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -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 diff --git a/core/README.md b/core/README.md new file mode 100644 index 0000000..d1460ac --- /dev/null +++ b/core/README.md @@ -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. diff --git a/core/src/lib.rs b/core/src/lib.rs index d0cb2ac..90cb906 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + mod errors; mod executor; diff --git a/dedust_api_client/README.md b/dedust_api_client/README.md index 28eea02..5c00d4b 100644 --- a/dedust_api_client/README.md +++ b/dedust_api_client/README.md @@ -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` 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> { +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:
`. + +## Supported Endpoints | Method | Supported | |------------------------------------------|-----------| @@ -28,3 +72,6 @@ headroom. Build public POD structs with `Default::default().with_(...)` or request parameter constructors, pass request parameters directly to `exec_api_v2` where `Into` 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. diff --git a/dedust_api_client/src/lib.rs b/dedust_api_client/src/lib.rs index fee339b..aa7b4d9 100644 --- a/dedust_api_client/src/lib.rs +++ b/dedust_api_client/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + pub use api_clients_core; // re-export pub mod api_client; pub mod api_v2; diff --git a/dedust_api_client/tests/test_api_v2.rs b/dedust_api_client/tests/test_api_v2.rs index 72b0a4e..4a6fb52 100644 --- a/dedust_api_client/tests/test_api_v2.rs +++ b/dedust_api_client/tests/test_api_v2.rs @@ -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 { 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(()) } diff --git a/stonfi_api_client/AGENTS.md b/stonfi_api_client/AGENTS.md index 4f08871..eafcd8b 100644 --- a/stonfi_api_client/AGENTS.md +++ b/stonfi_api_client/AGENTS.md @@ -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 @@ -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. @@ -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 diff --git a/stonfi_api_client/README.md b/stonfi_api_client/README.md index bd577f6..988ee76 100644 --- a/stonfi_api_client/README.md +++ b/stonfi_api_client/README.md @@ -1,4 +1,65 @@ -### https://api.ston.fi/swagger-ui +# stonfi_api_client + +Thin typed wrapper for the [STON.fi API v1 and public export endpoints](https://api.ston.fi/swagger-ui). + +Use this crate when an application needs typed access to STON.fi assets, pools, +farms, routers, swap/liquidity simulation, wallet views, stats, transactions, or +public export feeds. The crate does not execute swaps or manage transaction +submission; application code owns wallet signing, retries around business +workflows, persistence, and fallback behavior. + +## Usage + +```toml +[dependencies] +stonfi_api_client = "0.8" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +``` + +Run requests inside an async Tokio runtime. Pass request parameter structs +directly where `Into` or `Into` is implemented, and +match response enums with a wildcard arm. + +```rust,no_run +use stonfi_api_client::api_client::StonfiApiClient; +use stonfi_api_client::v1::{PoolsParams, V1Response}; + +# async fn example() -> Result<(), Box> { +let client = StonfiApiClient::builder().build()?; +let response = client.v1.exec(PoolsParams::new()).await?; + +match response { + V1Response::Pools(pools) => { + println!("pools: {}", pools.pool_list.len()); + } + _ => { + println!("unexpected STON.fi response variant"); + } +} +# Ok(()) +# } +``` + +Use `ExportApiClient` for public export feeds: + +```rust,no_run +use stonfi_api_client::api_client::StonfiApiClient; +use stonfi_api_client::export::{ExportRequest, ExportResponse}; + +# async fn example() -> Result<(), Box> { +let client = StonfiApiClient::builder().build()?; +let response = client.export.exec(ExportRequest::Cmc).await?; + +match response { + ExportResponse::Cmc(pools) => println!("CMC pools: {}", pools.len()), + _ => println!("unexpected STON.fi export response variant"), +} +# Ok(()) +# } +``` + +## Supported Endpoints + | Group | Method | Supported | |-------|-----------------------------------------------|----------| | AMM | /v1/assets | ✅ | @@ -8,11 +69,12 @@ | AMM | /v1/farms | ✅ | | AMM | /v1/farms/by_pool/{pool_addr_str} | ✅ | | AMM | /v1/farms/{addr_str} | ✅ | -| AMM | /v1/liquidity_provision/simulate | | +| AMM | /v1/jetton/{addr_str}/address | ✅ | +| AMM | /v1/liquidity_provision/simulate | ✅ | | AMM | /v1/markets | ✅ | | AMM | /v1/pools/query | ✅ | | AMM | /v1/pools | ✅ | -| AMM | /v1/pools/by_market/{asset0_str}/{asset1_str} | ✅ | +| AMM | /v1/pools/by_market/{asset_0_addr_str}/{asset_1_addr_str} | ✅ | | AMM | /v1/pools/{addr_str} | ✅ | | AMM | /v1/reverse_swap/simulate | ✅ | | AMM | /v1/routers | ✅ | @@ -28,9 +90,28 @@ | AMM | /v1/swap/status | ✅ | | AMM | /v1/transactions/query | ✅ | | AMM | /v1/transactions/{hash}/action_tree | ✅ | +| AMM | /v1/wallets/{addr_str}/assets | ✅ | +| AMM | /v1/wallets/{addr_str}/assets/{asset_str} | ✅ | +| AMM | /v1/wallets/{addr_str}/farms | ✅ | +| AMM | /v1/wallets/{addr_str}/farms/{farm_str} | ✅ | +| AMM | /v1/wallets/{addr_str}/fee_vaults | ✅ | +| AMM | /v1/wallets/{addr_str}/operations | ✅ | +| AMM | /v1/wallets/{addr_str}/pools | ✅ | +| AMM | /v1/wallets/{addr_str}/pools/{pool_str} | ✅ | +| AMM | /v1/wallets/{addr_str}/stakes | ✅ | +| AMM | /v1/wallets/{addr_str}/transactions/last | ✅ | +| Export | /export/cmc/v1 | ✅ | +| Export | /export/dexscreener/v1/asset/{address} | ✅ | +| Export | /export/dexscreener/v1/events | ✅ | +| Export | /export/dexscreener/v1/latest-block | ✅ | +| Export | /export/dexscreener/v1/pair/{address} | ✅ | Public request and response types are marked `#[non_exhaustive]` for semver headroom. Build public POD structs with `Default::default().with_(...)` or request parameter `new()` constructors, pass request parameters directly to -`V1ApiClient::exec` where `Into` is implemented, and include a -wildcard arm when matching response enums. +`V1ApiClient::exec` or `ExportApiClient::exec` where `Into` or +`Into` is implemented, and include a wildcard arm when matching +response enums. + +Live API tests hit STON.fi directly. Asset metadata, pool counts, prices, +transaction state, and optional transaction fields can drift with upstream data. diff --git a/stonfi_api_client/src/api_client.rs b/stonfi_api_client/src/api_client.rs index 62b7634..251b0dc 100644 --- a/stonfi_api_client/src/api_client.rs +++ b/stonfi_api_client/src/api_client.rs @@ -1,12 +1,16 @@ mod builder; use crate::api_client::builder::Builder; +use crate::export::ExportApiClient; use crate::v1::V1ApiClient; +pub const DEFAULT_API_URL: &str = "https://api.ston.fi"; pub const DEFAULT_API_V1_URL: &str = "https://api.ston.fi/v1"; #[derive(Clone)] +#[non_exhaustive] pub struct StonfiApiClient { pub v1: V1ApiClient, + pub export: ExportApiClient, } impl StonfiApiClient { diff --git a/stonfi_api_client/src/api_client/builder.rs b/stonfi_api_client/src/api_client/builder.rs index 3606e70..6114ca8 100644 --- a/stonfi_api_client/src/api_client/builder.rs +++ b/stonfi_api_client/src/api_client/builder.rs @@ -1,4 +1,5 @@ -use crate::api_client::{StonfiApiClient, DEFAULT_API_V1_URL}; +use crate::api_client::{StonfiApiClient, DEFAULT_API_URL, DEFAULT_API_V1_URL}; +use crate::export::ExportApiClient; use crate::v1::V1ApiClient; use api_clients_core::{ApiClientsResult, Executor}; use derive_setters::Setters; @@ -9,14 +10,18 @@ use std::sync::Arc; #[non_exhaustive] pub struct Builder { api_url: String, + export_api_url: String, executor: Option>, + export_executor: Option>, } impl Builder { pub(super) fn new() -> Self { Self { api_url: DEFAULT_API_V1_URL.to_string(), + export_api_url: DEFAULT_API_URL.to_string(), executor: None, + export_executor: None, } } @@ -25,8 +30,13 @@ impl Builder { Some(executor) => executor, None => Executor::builder(self.api_url).build()?.into(), }; + let export_executor = match self.export_executor { + Some(executor) => executor, + None => Executor::builder(self.export_api_url).build()?.into(), + }; let v1 = V1ApiClient::new(executor); - Ok(StonfiApiClient { v1 }) + let export = ExportApiClient::new(export_executor); + Ok(StonfiApiClient { v1, export }) } } diff --git a/stonfi_api_client/src/export.rs b/stonfi_api_client/src/export.rs new file mode 100644 index 0000000..327dd20 --- /dev/null +++ b/stonfi_api_client/src/export.rs @@ -0,0 +1,42 @@ +mod request; +mod response; +mod types; + +use api_clients_core::{ApiClientsResult, Executor}; +use std::sync::Arc; + +pub use request::*; +pub use response::*; +pub use types::*; + +#[derive(Clone)] +pub struct ExportApiClient { + executor: Arc, +} + +impl ExportApiClient { + pub(crate) fn new(executor: Arc) -> Self { Self { executor } } + + pub async fn exec(&self, request: REQUEST) -> ApiClientsResult + where + REQUEST: Into, + { + let request = request.into(); + let response = match &request { + ExportRequest::Cmc => ExportResponse::Cmc(self.executor.exec_get("export/cmc/v1").await?), + ExportRequest::DexscreenerLatestBlock => ExportResponse::DexscreenerLatestBlock( + self.executor.exec_get("export/dexscreener/v1/latest-block").await?, + ), + ExportRequest::DexscreenerAsset(address) => ExportResponse::DexscreenerAsset( + self.executor.exec_get(&format!("export/dexscreener/v1/asset/{address}")).await?, + ), + ExportRequest::DexscreenerPair(address) => ExportResponse::DexscreenerPair( + self.executor.exec_get(&format!("export/dexscreener/v1/pair/{address}")).await?, + ), + ExportRequest::DexscreenerEvents(params) => ExportResponse::DexscreenerEvents( + self.executor.exec_get_extra("export/dexscreener/v1/events", params, &[]).await?, + ), + }; + Ok(response) + } +} diff --git a/stonfi_api_client/src/export/request.rs b/stonfi_api_client/src/export/request.rs new file mode 100644 index 0000000..a723d93 --- /dev/null +++ b/stonfi_api_client/src/export/request.rs @@ -0,0 +1,36 @@ +use derive_more::From; +use derive_setters::Setters; +use serde_derive::Serialize; + +#[derive(Clone, From)] +#[non_exhaustive] +pub enum ExportRequest { + #[from(skip)] + Cmc, + #[from(skip)] + DexscreenerLatestBlock, + #[from(skip)] + DexscreenerAsset(String), + #[from(skip)] + DexscreenerPair(String), + DexscreenerEvents(DexscreenerEventsParams), +} + +impl From<&ExportRequest> for ExportRequest { + fn from(request: &ExportRequest) -> Self { request.clone() } +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct DexscreenerEventsParams { + #[serde(rename = "fromBlock")] + pub from_block: u32, + #[serde(rename = "toBlock")] + pub to_block: u32, +} + +impl DexscreenerEventsParams { + pub fn new(from_block: u32, to_block: u32) -> Self { Self { from_block, to_block } } +} diff --git a/stonfi_api_client/src/export/response.rs b/stonfi_api_client/src/export/response.rs new file mode 100644 index 0000000..3f129ef --- /dev/null +++ b/stonfi_api_client/src/export/response.rs @@ -0,0 +1,45 @@ +use crate::export::types::{CmcPoolStats, DexscreenerAsset, DexscreenerBlock, DexscreenerEvent, DexscreenerPair}; +use derive_more::From; +use derive_setters::Setters; +use serde_derive::Deserialize; +use std::collections::BTreeMap; + +#[derive(Deserialize, Debug, Clone, From)] +#[non_exhaustive] +pub enum ExportResponse { + Cmc(CmcResponse), + DexscreenerLatestBlock(DexscreenerLatestBlockResponse), + DexscreenerAsset(DexscreenerAssetResponse), + DexscreenerPair(DexscreenerPairResponse), + DexscreenerEvents(DexscreenerEventsResponse), +} + +pub type CmcResponse = BTreeMap; + +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct DexscreenerLatestBlockResponse { + pub block: DexscreenerBlock, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct DexscreenerEventsResponse { + pub events: Vec, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct DexscreenerAssetResponse { + pub asset: DexscreenerAsset, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct DexscreenerPairResponse { + pub pool: DexscreenerPair, +} diff --git a/stonfi_api_client/src/export/types.rs b/stonfi_api_client/src/export/types.rs new file mode 100644 index 0000000..fdb6a80 --- /dev/null +++ b/stonfi_api_client/src/export/types.rs @@ -0,0 +1,127 @@ +use derive_setters::Setters; +use serde_derive::Deserialize; + +// CMC export models. +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct CmcPoolStats { + pub base_id: String, + pub base_liquidity: String, + pub base_name: String, + pub base_symbol: String, + pub base_volume: String, + pub last_price: String, + pub quote_id: String, + pub quote_liquidity: String, + pub quote_name: String, + pub quote_symbol: String, + pub quote_volume: String, + pub url: String, +} + +// Dexscreener export models. +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(tag = "eventType", rename_all = "camelCase")] +#[non_exhaustive] +pub enum DexscreenerEvent { + #[serde(rename = "join")] + Join { + block: DexscreenerBlock, + #[serde(rename = "txnId")] + txn_id: String, + #[serde(rename = "txnIndex")] + txn_index: i64, + #[serde(rename = "eventIndex")] + event_index: u64, + maker: String, + #[serde(rename = "pairId")] + pair_id: String, + amount0: String, + amount1: String, + reserves: Option, + }, + #[serde(rename = "exit")] + Exit { + block: DexscreenerBlock, + #[serde(rename = "txnId")] + txn_id: String, + #[serde(rename = "txnIndex")] + txn_index: i64, + #[serde(rename = "eventIndex")] + event_index: u64, + maker: String, + #[serde(rename = "pairId")] + pair_id: String, + amount0: String, + amount1: String, + reserves: Option, + }, + #[serde(rename = "swap")] + Swap { + block: DexscreenerBlock, + #[serde(rename = "txnId")] + txn_id: String, + #[serde(rename = "txnIndex")] + txn_index: i64, + #[serde(rename = "eventIndex")] + event_index: u64, + maker: String, + #[serde(rename = "pairId")] + pair_id: String, + #[serde(rename = "amount0In")] + amount0_in: Option, + #[serde(rename = "amount0Out")] + amount0_out: Option, + #[serde(rename = "amount1In")] + amount1_in: Option, + #[serde(rename = "amount1Out")] + amount1_out: Option, + #[serde(rename = "priceNative")] + price_native: String, + reserves: Option, + }, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct DexscreenerBlock { + pub block_number: u32, + pub block_timestamp: i64, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct DexscreenerReserves { + pub asset0: String, + pub asset1: String, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct DexscreenerAsset { + pub circulating_supply: Option, + pub id: String, + pub name: String, + pub symbol: String, + pub total_supply: String, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct DexscreenerPair { + pub asset0_id: String, + pub asset1_id: String, + pub created_at_block_number: Option, + pub created_at_block_timestamp: Option, + pub created_at_txn_id: Option, + pub fee_bps: Option, + pub id: String, +} diff --git a/stonfi_api_client/src/lib.rs b/stonfi_api_client/src/lib.rs index 140eeaf..7047933 100644 --- a/stonfi_api_client/src/lib.rs +++ b/stonfi_api_client/src/lib.rs @@ -1,3 +1,6 @@ +#![doc = include_str!("../README.md")] + pub use api_clients_core; // re-export pub mod api_client; +pub mod export; pub mod v1; diff --git a/stonfi_api_client/src/v1.rs b/stonfi_api_client/src/v1.rs index 3b0eada..bec52cb 100644 --- a/stonfi_api_client/src/v1.rs +++ b/stonfi_api_client/src/v1.rs @@ -1,4 +1,3 @@ -mod actions; mod request; mod response; mod types; @@ -6,7 +5,6 @@ mod types; use api_clients_core::{ApiClientsResult, Executor}; use std::sync::Arc; -pub use actions::*; pub use request::*; pub use response::*; pub use types::*; @@ -47,6 +45,14 @@ impl V1ApiClient { V1Request::FarmByPool(params) => { V1Response::FarmByPool(self.executor.exec_get(&format!("farms/by_pool/{}", params.pool_address)).await?) }, + V1Request::JettonWalletAddress(params) => { + let path = format!("jetton/{}/address", params.asset_address); + let query = JettonWalletAddressQuery { owner_address: ¶ms.owner_address }; + V1Response::JettonWalletAddress(self.executor.exec_get_extra(&path, &query, &[]).await?) + }, + V1Request::LiquidityProvisionSimulate(params) => { + V1Response::LiquidityProvisionSimulate(self.executor.exec_post_qs("liquidity_provision/simulate", params, &[]).await?) + }, V1Request::Markets(params) => { V1Response::Markets(self.executor.exec_get_extra("markets", params, &[]).await?) }, @@ -105,6 +111,55 @@ impl V1ApiClient { V1Request::TransactionActionTree(hash) => { V1Response::TransactionActionTree(self.executor.exec_get(&format!("transactions/{hash}/action_tree")).await?) }, + V1Request::WalletAssets(params) => { + V1Response::WalletAssets(self.executor.exec_get(&format!("wallets/{}/assets", params.wallet_address)).await?) + }, + V1Request::WalletAsset(params) => { + let path = format!("wallets/{}/assets/{}", params.wallet_address, params.asset_address); + V1Response::WalletAsset(self.executor.exec_get(&path).await?) + }, + V1Request::WalletPools(params) => { + let path = format!("wallets/{}/pools", params.wallet_address); + let query = DexV2Query { dex_v2: params.dex_v2 }; + V1Response::WalletPools(self.executor.exec_get_extra(&path, &query, &[]).await?) + }, + V1Request::WalletPool(params) => { + let path = format!("wallets/{}/pools/{}", params.wallet_address, params.pool_address); + V1Response::WalletPool(self.executor.exec_get(&path).await?) + }, + V1Request::WalletFarms(params) => { + let path = format!("wallets/{}/farms", params.wallet_address); + let query = WalletFarmsQuery { dex_v2: params.dex_v2, only_active: params.only_active }; + V1Response::WalletFarms(self.executor.exec_get_extra(&path, &query, &[]).await?) + }, + V1Request::WalletFarm(params) => { + let path = format!("wallets/{}/farms/{}", params.wallet_address, params.farm_address); + V1Response::WalletFarm(self.executor.exec_get(&path).await?) + }, + V1Request::WalletStakes(params) => { + V1Response::WalletStakes(self.executor.exec_get(&format!("wallets/{}/stakes", params.wallet_address)).await?) + }, + V1Request::WalletOperations(params) => { + let path = format!("wallets/{}/operations", params.wallet_address); + let query = WalletOperationsQuery { + since: ¶ms.since, + until: ¶ms.until, + op_type: ¶ms.op_type, + dex_v2: params.dex_v2, + }; + V1Response::WalletOperations(self.executor.exec_get_extra(&path, &query, &[]).await?) + }, + V1Request::WalletTransactionsLast(params) => { + let path = format!("wallets/{}/transactions/last", params.wallet_address); + let query = WalletTransactionsLastQuery { + limit: params.limit, + min_tx_timestamp: params.min_tx_timestamp.as_deref(), + }; + V1Response::WalletTransactionsLast(self.executor.exec_get_extra(&path, &query, &[]).await?) + }, + V1Request::WalletFeeVaults(params) => { + V1Response::WalletFeeVaults(self.executor.exec_get(&format!("wallets/{}/fee_vaults", params.wallet_address)).await?) + }, }; Ok(response) } diff --git a/stonfi_api_client/src/v1/actions.rs b/stonfi_api_client/src/v1/actions.rs deleted file mode 100644 index ac62d23..0000000 --- a/stonfi_api_client/src/v1/actions.rs +++ /dev/null @@ -1,665 +0,0 @@ -use derive_setters::Setters; -use serde_derive::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct TxId { - pub lt: i64, - pub hash: String, - pub contract_address: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(untagged)] -#[allow(clippy::large_enum_variant)] -#[non_exhaustive] -pub enum Action { - Amm(AmmAction), - Farm(FarmAction), - Other(OtherAction), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "@type", rename_all = "snake_case")] -#[non_exhaustive] -pub enum AmmAction { - Swap(ActionData>), - ProvideLiquidity( - WithEffects< - ActionData>, - ProvideLiquidityEffect, - >, - ), - DirectAddLiquidity(ActionData>), - JettonBurn(ActionData>), - CollectFees(ActionData>), - WithdrawFee(ActionData>), - RefundLiquidity(ActionData>), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "@type", rename_all = "snake_case")] -#[non_exhaustive] -pub enum FarmAction { - FarmDeposit(ActionData>), - FarmWithdraw( - WithEffects< - ActionData>, - FarmWithdrawEffect, - >, - ), - FarmClaimRewards( - WithEffects< - ActionData>, - FarmClaimRewardsEffect, - >, - ), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "@type", rename_all = "snake_case")] -#[non_exhaustive] -pub enum OtherAction { - Other(ActionData), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct ActionData { - pub contract_major_version: Option, - pub destination: Option, - pub tx_payload: Payload, - pub status: Result, - pub prev_index: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct WithEffects { - #[serde(flatten)] - pub data: T, - - #[serde(default)] - pub effects: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -#[serde(tag = "@type", rename_all = "snake_case")] -#[non_exhaustive] -pub enum ActionStatus { - Completed(CompletedActionStatus), - #[default] - Pending, - Aborted, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct CompletedActionStatus { - pub tx_id: TxId, - pub success: bool, - - #[serde(flatten)] - pub data: Result, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum FarmMajorVersion { - V1, - V2, - V3, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum DexMajorVersion { - V1, - V2, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct SwapData { - // Common fields - pub opcode: u32, - pub query_id: u64, - pub ask_jetton_wallet_address: String, - pub min_ask_amount: String, - pub referral_address: Option, - - // V1-specific fields - pub user_wallet_address: Option, - - // V2-specific fields - pub refund_address: Option, - pub excesses_address: Option, - pub deadline: Option, - pub receiver_address: Option, - pub dex_forward_gas_amount: Option, - pub forward_gas_amount: Option, - - pub referral_val: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "exit_code", rename_all = "snake_case")] -#[non_exhaustive] -pub enum SwapResult { - #[serde(rename = "swap_ok")] - Ok { - ask_jetton_wallet: String, - ask_jetton: String, - ask_jetton_amount: String, - offer_jetton_wallet: String, - offer_jetton: String, - offer_jetton_amount: String, - fwd_gas: Option, - is_reversed: bool, - }, - #[serde(rename = "swap_ok_ref")] - OkRef { - ask_jetton_wallet: String, - ask_jetton: Option, - ask_jetton_amount: String, - offer_jetton_wallet: String, - offer_jetton: Option, - offer_jetton_amount: String, - ref_offer_jetton_wallet: String, - ref_ask_jetton_wallet: String, - ref_ask_amount: String, - fwd_gas: Option, - is_reversed: bool, - }, - #[serde(rename = "swap_refund_no_liq")] - RefundNoLiquidity { - ask_jetton_wallet: String, - ask_amount: String, - }, - #[serde(rename = "swap_refund_reserve_err")] - RefundReserveError { - ask_jetton_wallet: String, - ask_amount: String, - }, - #[serde(rename = "swap_refund_slippage")] - RefundSlippageError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_pool_locked")] - RefundPoolLockedError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_fee_out_of_bounds")] - RefundSwapFeeOutOfBoundError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_tx_expired")] - RefundTxExpiredError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_zero_output_err")] - RefundZeroOutputError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_invalid_caller_err")] - RefundInvalidCallerError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_insufficient_gas_err")] - RefundInsufficientGasError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_wrong_address_err")] - RefundWrongAddressError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_low_liquidity_err")] - RefundLowLiquidityError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_math_err")] - RefundMathError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_invalid_amount_err")] - RefundInvalidAmountError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_invalid_call_err")] - RefundInvalidCallError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_fee_out_range_err")] - RefundFeeOutRangeError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_invalid_token_err")] - RefundInvalidTokenError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_empty_not_allowed_err")] - RefundEmptyNotAllowedError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_max_in_ratio_err")] - RefundMaxInRatio { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_empty_cell_ratio_err")] - RefundEmptyCellRatio { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_cell_underflow_err")] - RefundCellUnderflowError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_wrong_workchain_err")] - RefundWrongWorkchainError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_refund_0_out")] - RefundZeroOutError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_x_out_of_bounds")] - XOutOfBoundsError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_y_out_of_bounds")] - YOutOfBoundsError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_product_out_of_bounds")] - ProductOfBoundsError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_invalid_exponent")] - InvalidExponentError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_out_of_bounds")] - OutOfBoundsError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_zero_base")] - ZeroBaseError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_negative_power")] - NegativePowerError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_zero_division")] - ZeroDivisionError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_invalid_input")] - InvalidInputError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_cmplement_out_of_range")] - CmplmentOutOfRangeError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_not_converge_range")] - NotConvergeError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "swap_fd_zero")] - DfZeroError { - offer_jetton_wallet: String, - offer_amount: String, - }, - #[serde(rename = "bounced")] - Bounce, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct ProvideLiquidityData { - // Common fields - pub opcode: u32, - pub query_id: u64, - pub router_wallet_address: String, - pub min_lp_out: String, - // V2-specific fields - pub refund_address: Option, - pub excesses_address: Option, - pub deadline: Option, - pub receiver_address: Option, - pub both_positive: Option, - pub dex_forward_gas_amount: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "exit_code", rename_all = "snake_case")] -#[allow(clippy::large_enum_variant)] -#[non_exhaustive] -pub enum ProvideLiquidityResult { - #[serde(rename = "provide_liquidity_ok")] - AddLiquidity { - amount0_out: String, - amount1_out: String, - min_lp_out: String, - fwd_amount: Option, - /// Owner of new liquidity tokens - to_address: Option, - refund_address: Option, - excess_address: Option, - }, - Bounce, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -#[serde(tag = "@type", rename_all = "snake_case")] -#[allow(clippy::large_enum_variant)] -#[non_exhaustive] -pub enum ProvideLiquidityEffect { - #[default] - Never, - CbAddLiquidity(EffectData), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "exit_code", rename_all = "snake_case")] -#[non_exhaustive] -pub enum CbAddLiquidityResult { - #[serde(rename = "add_liquidity_ok")] - AddLiquidity { - /// The amount of 0th asset provided - amount0_out: String, - /// The amount of 1st asset provided - amount1_out: String, - /// Minimal LP token amount to provide - min_lp_out: String, - /// Optional destination address - to_address: Option, - /// Optional forwarded amount - fwd_amount: Option, - /// Optional refund address - refund_address: Option, - /// Optional excess address - excess_address: Option, - }, - #[serde(rename = "mint_lp_ok")] - MintLp { - lp_out: String, - }, - Empty, - Bounce, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct EffectData { - pub status: ActionStatus, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "@type", rename_all = "snake_case")] -#[non_exhaustive] -pub enum UnknownActionStatus { - Completed {}, - Pending, - Aborted, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct DirectAddLiquidityData { - pub opcode: u32, - pub query_id: u64, - pub amount_0: String, - pub amount_1: String, - pub min_lp_to_mint: String, - - // V2-specific fields - pub dex_forward_gas_amount: Option, - pub user_wallet_address: Option, - pub refund_address: Option, - pub excesses_address: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct BurnData { - pub opcode: u32, - pub query_id: u64, - pub amount: String, - pub response_destination: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "exit_code", rename_all = "snake_case")] -#[non_exhaustive] -pub enum BurnNotificationResult { - #[serde(rename = "burn_ok")] - Ok { - from_address: String, - amount0_out: String, - amount1_out: String, - }, - Bounce, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct CollectFeesData { - pub opcode: u32, - pub query_id: u64, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct WithdrawFeesData { - pub opcode: u32, - pub query_id: u64, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct RefundLiquidityData { - pub opcode: u32, - pub query_id: u64, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct FarmDepositData { - pub opcode: u32, - pub query_id: u64, - pub owner_address: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct FarmWithdrawData { - pub opcode: u32, - pub query_id: u64, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct FarmClaimRewardsData { - pub opcode: u32, - pub query_id: u64, - pub claim_all: Option, - pub pool_index: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "exit_code", rename_all = "snake_case")] -#[non_exhaustive] -pub enum CollectFeesResult { - #[serde(rename = "collect_fees_ok")] - Ok { - protocol_fee_address: String, - collected_token0_protocol_fee: String, - collected_token1_protocol_fee: String, - }, - - #[serde(rename = "collect_fees_ok_with_rewards")] - OkWithReward { - protocol_fee_address: String, - collected_token0_protocol_fee: String, - collected_token1_protocol_fee: String, - sender_address: String, - reward0: String, - reward1: String, - }, - - Bounce, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "exit_code", rename_all = "snake_case")] -#[non_exhaustive] -pub enum WithdrawFeeResult { - Ok { - amount_out: String, - token_address: String, - to_address: String, - }, - Bounce, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "exit_code", rename_all = "snake_case")] -#[non_exhaustive] -pub enum RefundResult { - #[serde(rename = "refund_ok")] - Ok { - user_address: String, - amount0_out: String, - router_jetton0_address: String, - amount1_out: String, - router_jetton1_address: String, - }, - Bounce, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "exit_code", rename_all = "snake_case")] -#[non_exhaustive] -pub enum FarmMinterStakeResult { - #[serde(rename = "farm_minter_stake_ok")] - Ok {}, - Bounce, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "exit_code", rename_all = "snake_case")] -#[non_exhaustive] -pub enum FarmNftClaimRewardsResult { - #[serde(rename = "farm_nft_claim_rewards_ok")] - Ok {}, - Bounce, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "exit_code", rename_all = "snake_case")] -#[non_exhaustive] -pub enum FarmNftUnstakeResult { - #[serde(rename = "farm_nft_unstake_ok")] - Ok {}, - Bounce, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -#[serde(tag = "@type", rename_all = "snake_case")] -#[non_exhaustive] -pub enum FarmWithdrawEffect { - #[default] - Never, - JettonTransfer(EffectData), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -#[serde(tag = "@type", rename_all = "snake_case")] -#[non_exhaustive] -pub enum FarmClaimRewardsEffect { - #[default] - Never, - JettonTransfer(EffectData), -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(tag = "exit_code", rename_all = "snake_case")] -#[non_exhaustive] -pub enum JettonTransferResult { - Ok(JettonTransferData), - Bounce, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct JettonTransferData { - pub opcode: u32, - pub query_id: u64, - pub amount: String, - pub destination: String, - pub response_destination: String, - pub forward_ton_amount: String, - pub jetton_master: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -#[non_exhaustive] -pub enum OtherTxPayload { - #[serde(rename = "unknown")] - Invalid { opcode: u32 }, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] -#[non_exhaustive] -pub struct Blank; diff --git a/stonfi_api_client/src/v1/request.rs b/stonfi_api_client/src/v1/request.rs index 8397bea..da96584 100644 --- a/stonfi_api_client/src/v1/request.rs +++ b/stonfi_api_client/src/v1/request.rs @@ -1,6 +1,6 @@ use derive_more::From; use derive_setters::Setters; -use serde_derive::Serialize; +use serde_derive::{Deserialize, Serialize}; #[derive(Clone, From)] #[non_exhaustive] @@ -15,6 +15,8 @@ pub enum V1Request { #[from(skip)] Farm(String), FarmByPool(FarmByPoolParams), + JettonWalletAddress(JettonWalletAddressParams), + LiquidityProvisionSimulate(LiquidityProvisionSimulateParams), Markets(MarketsParams), PoolQuery(PoolQueryParams), Pools(PoolsParams), @@ -38,6 +40,16 @@ pub enum V1Request { TransactionQuery(TransactionQueryParams), #[from(skip)] TransactionActionTree(String), + WalletAsset(WalletAssetParams), + WalletAssets(WalletAssetsParams), + WalletFarm(WalletFarmParams), + WalletFarms(WalletFarmsParams), + WalletFeeVaults(WalletFeeVaultsParams), + WalletOperations(WalletOperationsParams), + WalletPool(WalletPoolParams), + WalletPools(WalletPoolsParams), + WalletStakes(WalletStakesParams), + WalletTransactionsLast(WalletTransactionsLastParams), } #[serde_with::skip_serializing_none] @@ -365,6 +377,72 @@ impl StatsFeesParams { } } +#[derive(Serialize, Clone, Default, Setters)] +#[setters(prefix = "with_")] +#[non_exhaustive] +pub struct JettonWalletAddressParams { + pub asset_address: String, + pub owner_address: String, +} + +impl JettonWalletAddressParams { + pub fn new(asset_address: impl Into, owner_address: impl Into) -> Self { + Self { + asset_address: asset_address.into(), + owner_address: owner_address.into(), + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)] +#[serde(rename_all = "PascalCase")] +#[non_exhaustive] +pub enum LiquidityProvisionType { + Initial, + #[default] + Balanced, + Arbitrary, +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct LiquidityProvisionSimulateParams { + pub provision_type: LiquidityProvisionType, + pub pool_address: Option, + pub wallet_address: Option, + pub token_a: String, + pub token_b: String, + pub token_a_units: Option, + pub token_b_units: Option, + pub slippage_tolerance: String, +} + +impl LiquidityProvisionSimulateParams { + pub fn new( + provision_type: LiquidityProvisionType, + token_a: impl Into, + token_b: impl Into, + slippage_tolerance: impl Into, + ) -> Self { + Self { + provision_type, + pool_address: None, + wallet_address: None, + token_a: token_a.into(), + token_b: token_b.into(), + token_a_units: None, + token_b_units: None, + slippage_tolerance: slippage_tolerance.into(), + } + } +} + +impl Default for LiquidityProvisionSimulateParams { + fn default() -> Self { Self::new(LiquidityProvisionType::Balanced, "", "", "") } +} + #[serde_with::skip_serializing_none] #[derive(Serialize, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] @@ -420,6 +498,226 @@ impl TransactionQueryParams { pub fn new() -> Self { Self::default() } } +#[derive(Serialize, Clone, Default, Setters)] +#[setters(prefix = "with_")] +#[non_exhaustive] +pub struct WalletAssetsParams { + pub wallet_address: String, +} + +impl WalletAssetsParams { + pub fn new(wallet_address: impl Into) -> Self { + Self { + wallet_address: wallet_address.into(), + } + } +} + +#[derive(Serialize, Clone, Default, Setters)] +#[setters(prefix = "with_")] +#[non_exhaustive] +pub struct WalletAssetParams { + pub wallet_address: String, + pub asset_address: String, +} + +impl WalletAssetParams { + pub fn new(wallet_address: impl Into, asset_address: impl Into) -> Self { + Self { + wallet_address: wallet_address.into(), + asset_address: asset_address.into(), + } + } +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WalletPoolsParams { + pub wallet_address: String, + pub dex_v2: Option, +} + +impl WalletPoolsParams { + pub fn new(wallet_address: impl Into) -> Self { + Self { + wallet_address: wallet_address.into(), + dex_v2: Some(true), + } + } +} + +impl Default for WalletPoolsParams { + fn default() -> Self { Self::new("") } +} + +#[derive(Serialize, Clone, Default, Setters)] +#[setters(prefix = "with_")] +#[non_exhaustive] +pub struct WalletPoolParams { + pub wallet_address: String, + pub pool_address: String, +} + +impl WalletPoolParams { + pub fn new(wallet_address: impl Into, pool_address: impl Into) -> Self { + Self { + wallet_address: wallet_address.into(), + pool_address: pool_address.into(), + } + } +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WalletFarmsParams { + pub wallet_address: String, + pub dex_v2: Option, + pub only_active: Option, +} + +impl WalletFarmsParams { + pub fn new(wallet_address: impl Into) -> Self { + Self { + wallet_address: wallet_address.into(), + dex_v2: Some(true), + only_active: None, + } + } +} + +impl Default for WalletFarmsParams { + fn default() -> Self { Self::new("") } +} + +#[derive(Serialize, Clone, Default, Setters)] +#[setters(prefix = "with_")] +#[non_exhaustive] +pub struct WalletFarmParams { + pub wallet_address: String, + pub farm_address: String, +} + +impl WalletFarmParams { + pub fn new(wallet_address: impl Into, farm_address: impl Into) -> Self { + Self { + wallet_address: wallet_address.into(), + farm_address: farm_address.into(), + } + } +} + +#[derive(Serialize, Clone, Default, Setters)] +#[setters(prefix = "with_")] +#[non_exhaustive] +pub struct WalletStakesParams { + pub wallet_address: String, +} + +impl WalletStakesParams { + pub fn new(wallet_address: impl Into) -> Self { + Self { + wallet_address: wallet_address.into(), + } + } +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WalletOperationsParams { + pub wallet_address: String, + pub since: String, + pub until: String, + pub op_type: Vec, + pub dex_v2: Option, +} + +impl WalletOperationsParams { + pub fn new(wallet_address: impl Into, since: impl Into, until: impl Into) -> Self { + Self { + wallet_address: wallet_address.into(), + since: since.into(), + until: until.into(), + op_type: Vec::new(), + dex_v2: Some(true), + } + } +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WalletTransactionsLastParams { + pub wallet_address: String, + pub limit: Option, + pub min_tx_timestamp: Option, +} + +impl WalletTransactionsLastParams { + pub fn new(wallet_address: impl Into) -> Self { + Self { + wallet_address: wallet_address.into(), + limit: None, + min_tx_timestamp: None, + } + } +} + +#[derive(Serialize, Clone, Default, Setters)] +#[setters(prefix = "with_")] +#[non_exhaustive] +pub struct WalletFeeVaultsParams { + pub wallet_address: String, +} + +impl WalletFeeVaultsParams { + pub fn new(wallet_address: impl Into) -> Self { + Self { + wallet_address: wallet_address.into(), + } + } +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize)] +pub(super) struct DexV2Query { + pub(super) dex_v2: Option, +} + +#[derive(Serialize)] +pub(super) struct JettonWalletAddressQuery<'a> { + pub(super) owner_address: &'a str, +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize)] +pub(super) struct WalletFarmsQuery { + pub(super) dex_v2: Option, + pub(super) only_active: Option, +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize)] +pub(super) struct WalletOperationsQuery<'a> { + pub(super) since: &'a str, + pub(super) until: &'a str, + pub(super) op_type: &'a [String], + pub(super) dex_v2: Option, +} + +#[serde_with::skip_serializing_none] +#[derive(Serialize)] +pub(super) struct WalletTransactionsLastQuery<'a> { + pub(super) limit: Option, + pub(super) min_tx_timestamp: Option<&'a str>, +} + impl From<&V1Request> for V1Request { fn from(request: &V1Request) -> Self { request.clone() } } diff --git a/stonfi_api_client/src/v1/response.rs b/stonfi_api_client/src/v1/response.rs index fad4b78..a831bd4 100644 --- a/stonfi_api_client/src/v1/response.rs +++ b/stonfi_api_client/src/v1/response.rs @@ -1,9 +1,12 @@ -use crate::v1::types::{Asset, Farm, Pool, QueryAsset, Router}; -use crate::v1::{TransactionActionTree, TxId}; +use crate::v1::request::LiquidityProvisionType; +use crate::v1::types::{ + Asset, AssetFeeStats, DexStats, Farm, FeeAccrual, FeeWithdrawal, Pool, PoolStats, QueryAsset, Router, + StatsOperationInfo, SwapStatus, TransactionActionTree, TxId, WalletFeeVault, WalletOperationInfo, WalletStakeNft, + WalletTransactionId, +}; use derive_more::From; use derive_setters::Setters; use serde_derive::Deserialize; -use std::collections::BTreeMap; #[macro_export] macro_rules! unwrap_response { @@ -20,6 +23,7 @@ macro_rules! unwrap_response { } #[derive(Deserialize, Debug, Clone, From)] +#[allow(clippy::large_enum_variant)] #[non_exhaustive] pub enum V1Response { Assets(AssetsResponse), @@ -30,6 +34,8 @@ pub enum V1Response { Farm(FarmResponse), #[from(skip)] FarmByPool(FarmsResponse), + JettonWalletAddress(AddressResponse), + LiquidityProvisionSimulate(LiquidityProvisionSimulateResponse), Markets(MarketsResponse), Pools(PoolsResponse), #[from(skip)] @@ -50,6 +56,22 @@ pub enum V1Response { StatsStaking(StatsStakingResponse), TransactionActionTree(TransactionActionTreeResponse), TransactionQuery(TransactionQueryResponse), + #[from(skip)] + WalletAsset(AssetResponse), + #[from(skip)] + WalletAssets(AssetsResponse), + #[from(skip)] + WalletFarm(FarmResponse), + #[from(skip)] + WalletFarms(FarmsResponse), + WalletFeeVaults(WalletFeeVaultsResponse), + WalletOperations(WalletOperationsResponse), + #[from(skip)] + WalletPool(PoolResponse), + #[from(skip)] + WalletPools(PoolsResponse), + WalletStakes(WalletStakesResponse), + WalletTransactionsLast(WalletTransactionsLastResponse), } #[derive(Deserialize, Debug, Clone, Default, Setters)] @@ -94,6 +116,13 @@ pub struct FarmResponse { pub farm: Farm, } +#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct AddressResponse { + pub address: String, +} + #[derive(Deserialize, Debug, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] @@ -158,31 +187,6 @@ pub enum SwapStatusResponse { NotFound, } -#[derive(Deserialize, Debug, Clone, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct SwapStatus { - pub address: String, - pub balance_deltas: SwapStatusBalanceDeltas, - pub coins: String, - pub exit_code: String, - pub logical_time: String, - pub query_id: String, - pub tx_hash: String, -} - -#[derive(Deserialize, Debug, Clone)] -#[serde(untagged)] -#[non_exhaustive] -pub enum SwapStatusBalanceDeltas { - Map(BTreeMap), - Text(String), -} - -impl Default for SwapStatusBalanceDeltas { - fn default() -> Self { Self::Map(BTreeMap::new()) } -} - #[derive(Deserialize, Debug, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] @@ -192,16 +196,6 @@ pub struct StatsDexResponse { pub stats: DexStats, } -#[derive(Deserialize, Debug, Clone, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct DexStats { - pub trades: u64, - pub tvl: String, - pub unique_wallets: u64, - pub volume_usd: String, -} - #[derive(Deserialize, Debug, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] @@ -209,13 +203,6 @@ pub struct StatsFeeAccrualsResponse { pub operations: Vec, } -#[derive(Deserialize, Debug, Clone, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct FeeAccrual { - pub pool_address: String, -} - #[derive(Deserialize, Debug, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] @@ -223,13 +210,6 @@ pub struct StatsFeeWithdrawalsResponse { pub withdrawals: Vec, } -#[derive(Deserialize, Debug, Clone, Default, Setters)] -#[setters(prefix = "with_", strip_option)] -#[non_exhaustive] -pub struct FeeWithdrawal { - pub vault_address: String, -} - #[derive(Deserialize, Debug, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] @@ -244,68 +224,96 @@ pub struct StatsFeesResponse { #[derive(Deserialize, Debug, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] -pub struct AssetFeeStats { - pub accrued: String, - pub accrued_usd: Option, - pub asset_address: String, - pub withdrawn: String, - pub withdrawn_usd: Option, +pub struct StatsOperationsResponse { + pub operations: Vec, } #[derive(Deserialize, Debug, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] -pub struct StatsOperationsResponse { - pub operations: Vec, +pub struct StatsPoolResponse { + pub since: String, + pub until: String, + pub unique_wallets_count: u64, + pub stats: Vec, } #[derive(Deserialize, Debug, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] -pub struct StatsOperationInfo { - pub operation: StatsOperation, +pub struct StatsStakingResponse { + pub gemston_total_supply: String, + pub ston_price_usd: String, + pub ston_total_supply: String, + pub total_staked_ston: String, } #[derive(Deserialize, Debug, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] -pub struct StatsOperation { - pub pool_address: String, +pub struct TransactionQueryResponse { + pub tx_id: Option, + pub wallet_seqno: Option, } +pub type TransactionActionTreeResponse = TransactionActionTree; + #[derive(Deserialize, Debug, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] -pub struct StatsPoolResponse { - pub since: String, - pub until: String, - pub unique_wallets_count: u64, - pub stats: Vec, +pub struct LiquidityProvisionSimulateResponse { + pub estimated_lp_units: String, + pub estimated_token_a_rate: String, + pub estimated_token_a_units: String, + pub estimated_token_b_rate: String, + pub estimated_token_b_units: String, + pub lp_account_address: Option, + pub lp_account_token_a_balance: Option, + pub lp_account_token_b_balance: Option, + pub lp_total_supply: String, + pub min_lp_units: String, + pub min_token_a_units: String, + pub min_token_b_units: String, + pub pool_address: String, + pub price_impact: String, + pub provision_type: LiquidityProvisionType, + pub router: Router, + pub router_address: String, + pub token_a: String, + pub token_a_units: String, + pub token_b: String, + pub token_b_units: String, } -#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] -pub struct PoolStats { - pub pool_address: String, +pub struct WalletFeeVaultsResponse { + pub vault_list: Vec, } #[derive(Deserialize, Debug, Clone, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] -pub struct StatsStakingResponse { - pub gemston_total_supply: String, - pub ston_price_usd: String, - pub ston_total_supply: String, - pub total_staked_ston: String, +pub struct WalletOperationsResponse { + pub operations: Vec, } -#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] -pub struct TransactionQueryResponse { - pub tx_id: Option, - pub wallet_seqno: Option, +pub struct WalletStakesResponse { + pub minted_gemston: String, + pub nfts: Option>, + pub staked_ston: String, + pub ston_balance: String, + pub voting_power: String, } -pub type TransactionActionTreeResponse = TransactionActionTree; +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WalletTransactionsLastResponse { + pub seqno: u32, + pub tx_list: Vec, +} diff --git a/stonfi_api_client/src/v1/types.rs b/stonfi_api_client/src/v1/types.rs index 10a90fc..fca0d24 100644 --- a/stonfi_api_client/src/v1/types.rs +++ b/stonfi_api_client/src/v1/types.rs @@ -1,6 +1,6 @@ -use crate::v1::actions::{Action, TxId}; use derive_setters::Setters; use serde_derive::{Deserialize, Serialize}; +use std::collections::BTreeMap; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] #[setters(prefix = "with_", strip_option)] @@ -137,6 +137,670 @@ pub struct Farm { pub status: String, } +// Transaction action models. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct TxId { + pub lt: i64, + pub hash: String, + pub contract_address: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(untagged)] +#[allow(clippy::large_enum_variant)] +#[non_exhaustive] +pub enum Action { + Amm(AmmAction), + Farm(FarmAction), + Other(OtherAction), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "@type", rename_all = "snake_case")] +#[non_exhaustive] +pub enum AmmAction { + Swap(ActionData>), + ProvideLiquidity( + WithEffects< + ActionData>, + ProvideLiquidityEffect, + >, + ), + DirectAddLiquidity(ActionData>), + JettonBurn(ActionData>), + CollectFees(ActionData>), + WithdrawFee(ActionData>), + RefundLiquidity(ActionData>), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "@type", rename_all = "snake_case")] +#[non_exhaustive] +pub enum FarmAction { + FarmDeposit(ActionData>), + FarmWithdraw( + WithEffects< + ActionData>, + FarmWithdrawEffect, + >, + ), + FarmClaimRewards( + WithEffects< + ActionData>, + FarmClaimRewardsEffect, + >, + ), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "@type", rename_all = "snake_case")] +#[non_exhaustive] +pub enum OtherAction { + Other(ActionData), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct ActionData { + pub contract_major_version: Option, + pub destination: Option, + pub tx_payload: Payload, + pub status: Result, + pub prev_index: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WithEffects { + #[serde(flatten)] + pub data: T, + + #[serde(default)] + pub effects: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(tag = "@type", rename_all = "snake_case")] +#[non_exhaustive] +pub enum ActionStatus { + Completed(CompletedActionStatus), + #[default] + Pending, + Aborted, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct CompletedActionStatus { + pub tx_id: TxId, + pub success: bool, + + #[serde(flatten)] + pub data: Result, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum FarmMajorVersion { + V1, + V2, + V3, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum DexMajorVersion { + V1, + V2, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct SwapData { + // Common fields + pub opcode: u32, + pub query_id: u64, + pub ask_jetton_wallet_address: String, + pub min_ask_amount: String, + pub referral_address: Option, + + // V1-specific fields + pub user_wallet_address: Option, + + // V2-specific fields + pub refund_address: Option, + pub excesses_address: Option, + pub deadline: Option, + pub receiver_address: Option, + pub dex_forward_gas_amount: Option, + pub forward_gas_amount: Option, + + pub referral_val: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "exit_code", rename_all = "snake_case")] +#[non_exhaustive] +pub enum SwapResult { + #[serde(rename = "swap_ok")] + Ok { + ask_jetton_wallet: String, + ask_jetton: String, + ask_jetton_amount: String, + offer_jetton_wallet: String, + offer_jetton: String, + offer_jetton_amount: String, + fwd_gas: Option, + is_reversed: bool, + }, + #[serde(rename = "swap_ok_ref")] + OkRef { + ask_jetton_wallet: String, + ask_jetton: Option, + ask_jetton_amount: String, + offer_jetton_wallet: String, + offer_jetton: Option, + offer_jetton_amount: String, + ref_offer_jetton_wallet: String, + ref_ask_jetton_wallet: String, + ref_ask_amount: String, + fwd_gas: Option, + is_reversed: bool, + }, + #[serde(rename = "swap_refund_no_liq")] + RefundNoLiquidity { + ask_jetton_wallet: String, + ask_amount: String, + }, + #[serde(rename = "swap_refund_reserve_err")] + RefundReserveError { + ask_jetton_wallet: String, + ask_amount: String, + }, + #[serde(rename = "swap_refund_slippage")] + RefundSlippageError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_pool_locked")] + RefundPoolLockedError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_fee_out_of_bounds")] + RefundSwapFeeOutOfBoundError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_tx_expired")] + RefundTxExpiredError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_zero_output_err")] + RefundZeroOutputError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_invalid_caller_err")] + RefundInvalidCallerError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_insufficient_gas_err")] + RefundInsufficientGasError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_wrong_address_err")] + RefundWrongAddressError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_low_liquidity_err")] + RefundLowLiquidityError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_math_err")] + RefundMathError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_invalid_amount_err")] + RefundInvalidAmountError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_invalid_call_err")] + RefundInvalidCallError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_fee_out_range_err")] + RefundFeeOutRangeError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_invalid_token_err")] + RefundInvalidTokenError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_empty_not_allowed_err")] + RefundEmptyNotAllowedError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_max_in_ratio_err")] + RefundMaxInRatio { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_empty_cell_ratio_err")] + RefundEmptyCellRatio { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_cell_underflow_err")] + RefundCellUnderflowError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_wrong_workchain_err")] + RefundWrongWorkchainError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_refund_0_out")] + RefundZeroOutError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_x_out_of_bounds")] + XOutOfBoundsError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_y_out_of_bounds")] + YOutOfBoundsError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_product_out_of_bounds")] + ProductOfBoundsError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_invalid_exponent")] + InvalidExponentError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_out_of_bounds")] + OutOfBoundsError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_zero_base")] + ZeroBaseError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_negative_power")] + NegativePowerError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_zero_division")] + ZeroDivisionError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_invalid_input")] + InvalidInputError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_cmplement_out_of_range")] + CmplmentOutOfRangeError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_not_converge_range")] + NotConvergeError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "swap_fd_zero")] + DfZeroError { + offer_jetton_wallet: String, + offer_amount: String, + }, + #[serde(rename = "bounced")] + Bounce, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct ProvideLiquidityData { + // Common fields + pub opcode: u32, + pub query_id: u64, + pub router_wallet_address: String, + pub min_lp_out: String, + // V2-specific fields + pub refund_address: Option, + pub excesses_address: Option, + pub deadline: Option, + pub receiver_address: Option, + pub both_positive: Option, + pub dex_forward_gas_amount: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "exit_code", rename_all = "snake_case")] +#[allow(clippy::large_enum_variant)] +#[non_exhaustive] +pub enum ProvideLiquidityResult { + #[serde(rename = "provide_liquidity_ok")] + AddLiquidity { + amount0_out: String, + amount1_out: String, + min_lp_out: String, + fwd_amount: Option, + /// Owner of new liquidity tokens + to_address: Option, + refund_address: Option, + excess_address: Option, + }, + Bounce, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(tag = "@type", rename_all = "snake_case")] +#[allow(clippy::large_enum_variant)] +#[non_exhaustive] +pub enum ProvideLiquidityEffect { + #[default] + Never, + CbAddLiquidity(EffectData), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "exit_code", rename_all = "snake_case")] +#[non_exhaustive] +pub enum CbAddLiquidityResult { + #[serde(rename = "add_liquidity_ok")] + AddLiquidity { + /// The amount of 0th asset provided + amount0_out: String, + /// The amount of 1st asset provided + amount1_out: String, + /// Minimal LP token amount to provide + min_lp_out: String, + /// Optional destination address + to_address: Option, + /// Optional forwarded amount + fwd_amount: Option, + /// Optional refund address + refund_address: Option, + /// Optional excess address + excess_address: Option, + }, + #[serde(rename = "mint_lp_ok")] + MintLp { + lp_out: String, + }, + Empty, + Bounce, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct EffectData { + pub status: ActionStatus, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "@type", rename_all = "snake_case")] +#[non_exhaustive] +pub enum UnknownActionStatus { + Completed {}, + Pending, + Aborted, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct DirectAddLiquidityData { + pub opcode: u32, + pub query_id: u64, + pub amount_0: String, + pub amount_1: String, + pub min_lp_to_mint: String, + + // V2-specific fields + pub dex_forward_gas_amount: Option, + pub user_wallet_address: Option, + pub refund_address: Option, + pub excesses_address: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct BurnData { + pub opcode: u32, + pub query_id: u64, + pub amount: String, + pub response_destination: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "exit_code", rename_all = "snake_case")] +#[non_exhaustive] +pub enum BurnNotificationResult { + #[serde(rename = "burn_ok")] + Ok { + from_address: String, + amount0_out: String, + amount1_out: String, + }, + Bounce, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct CollectFeesData { + pub opcode: u32, + pub query_id: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WithdrawFeesData { + pub opcode: u32, + pub query_id: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct RefundLiquidityData { + pub opcode: u32, + pub query_id: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct FarmDepositData { + pub opcode: u32, + pub query_id: u64, + pub owner_address: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct FarmWithdrawData { + pub opcode: u32, + pub query_id: u64, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct FarmClaimRewardsData { + pub opcode: u32, + pub query_id: u64, + pub claim_all: Option, + pub pool_index: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "exit_code", rename_all = "snake_case")] +#[non_exhaustive] +pub enum CollectFeesResult { + #[serde(rename = "collect_fees_ok")] + Ok { + protocol_fee_address: String, + collected_token0_protocol_fee: String, + collected_token1_protocol_fee: String, + }, + + #[serde(rename = "collect_fees_ok_with_rewards")] + OkWithReward { + protocol_fee_address: String, + collected_token0_protocol_fee: String, + collected_token1_protocol_fee: String, + sender_address: String, + reward0: String, + reward1: String, + }, + + Bounce, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "exit_code", rename_all = "snake_case")] +#[non_exhaustive] +pub enum WithdrawFeeResult { + Ok { + amount_out: String, + token_address: String, + to_address: String, + }, + Bounce, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "exit_code", rename_all = "snake_case")] +#[non_exhaustive] +pub enum RefundResult { + #[serde(rename = "refund_ok")] + Ok { + user_address: String, + amount0_out: String, + router_jetton0_address: String, + amount1_out: String, + router_jetton1_address: String, + }, + Bounce, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "exit_code", rename_all = "snake_case")] +#[non_exhaustive] +pub enum FarmMinterStakeResult { + #[serde(rename = "farm_minter_stake_ok")] + Ok {}, + Bounce, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "exit_code", rename_all = "snake_case")] +#[non_exhaustive] +pub enum FarmNftClaimRewardsResult { + #[serde(rename = "farm_nft_claim_rewards_ok")] + Ok {}, + Bounce, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "exit_code", rename_all = "snake_case")] +#[non_exhaustive] +pub enum FarmNftUnstakeResult { + #[serde(rename = "farm_nft_unstake_ok")] + Ok {}, + Bounce, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(tag = "@type", rename_all = "snake_case")] +#[non_exhaustive] +pub enum FarmWithdrawEffect { + #[default] + Never, + JettonTransfer(EffectData), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(tag = "@type", rename_all = "snake_case")] +#[non_exhaustive] +pub enum FarmClaimRewardsEffect { + #[default] + Never, + JettonTransfer(EffectData), +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "exit_code", rename_all = "snake_case")] +#[non_exhaustive] +pub enum JettonTransferResult { + Ok(JettonTransferData), + Bounce, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct JettonTransferData { + pub opcode: u32, + pub query_id: u64, + pub amount: String, + pub destination: String, + pub response_destination: String, + pub forward_ton_amount: String, + pub jetton_master: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[non_exhaustive] +pub enum OtherTxPayload { + #[serde(rename = "unknown")] + Invalid { opcode: u32 }, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +#[non_exhaustive] +pub struct Blank; + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, Setters)] #[setters(prefix = "with_", strip_option)] #[non_exhaustive] @@ -145,3 +809,164 @@ pub struct TransactionActionTree { pub initial_tx_id: TxId, pub actions: Vec, } + +// Swap status models. +#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct SwapStatus { + pub address: String, + pub balance_deltas: SwapStatusBalanceDeltas, + pub coins: String, + pub exit_code: String, + pub logical_time: String, + pub query_id: String, + pub tx_hash: String, +} + +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged)] +#[non_exhaustive] +pub enum SwapStatusBalanceDeltas { + Map(BTreeMap), + Text(String), +} + +impl Default for SwapStatusBalanceDeltas { + fn default() -> Self { Self::Map(BTreeMap::new()) } +} + +// Stats models. +#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct DexStats { + pub trades: u64, + pub tvl: String, + pub unique_wallets: u64, + pub volume_usd: String, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct FeeAccrual { + pub pool_address: String, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct FeeWithdrawal { + pub vault_address: String, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct AssetFeeStats { + pub accrued: String, + pub accrued_usd: Option, + pub asset_address: String, + pub withdrawn: String, + pub withdrawn_usd: Option, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct StatsOperationInfo { + pub operation: StatsOperation, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct StatsOperation { + pub pool_address: String, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct PoolStats { + pub pool_address: String, +} + +// Wallet models. +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WalletFeeVault { + pub asset_address: String, + pub balance: String, + pub router_address: String, + pub vault_address: String, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WalletOperationInfo { + pub asset0_info: Asset, + pub asset1_info: Asset, + pub operation: WalletOperation, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WalletOperation { + pub asset0_address: String, + pub asset0_amount: String, + pub asset0_delta: String, + pub asset0_reserve: String, + pub asset1_address: String, + pub asset1_amount: String, + pub asset1_delta: String, + pub asset1_reserve: String, + pub destination_wallet_address: String, + pub exit_code: String, + pub fee_asset_address: Option, + pub lp_fee_amount: String, + pub lp_token_delta: String, + pub lp_token_supply: String, + pub operation_type: String, + pub pool_address: String, + pub pool_tx_hash: String, + pub pool_tx_lt: i64, + pub pool_tx_timestamp: String, + pub protocol_fee_amount: String, + pub referral_address: Option, + pub referral_fee_amount: String, + pub router_address: String, + pub success: bool, + pub wallet_address: String, + pub wallet_tx_hash: String, + pub wallet_tx_lt: String, + pub wallet_tx_timestamp: String, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WalletStakeNft { + pub address: String, + pub image_url: String, + pub min_unstaking_timestamp: String, + pub minted_gemston: String, + pub staked_tokens: String, + pub staking_timestamp: String, + pub status: String, + pub unstake_timestamp: Option, + pub voting_power: String, +} + +#[derive(Deserialize, Debug, Clone, Default, Setters, PartialEq, Eq)] +#[setters(prefix = "with_", strip_option)] +#[non_exhaustive] +pub struct WalletTransactionId { + pub contract_address: Option, + pub hash: String, + pub lt: i64, +} diff --git a/stonfi_api_client/tests/test_api.rs b/stonfi_api_client/tests/test_api_v1.rs similarity index 70% rename from stonfi_api_client/tests/test_api.rs rename to stonfi_api_client/tests/test_api_v1.rs index b0de8a9..83a527f 100644 --- a/stonfi_api_client/tests/test_api.rs +++ b/stonfi_api_client/tests/test_api_v1.rs @@ -13,6 +13,8 @@ fn init_env() -> Result { Ok(StonfiApiClient::builder().with_executor(Arc::new(executor)).build()?) } +const WALLET_ADDRESS_WITH_POSITIONS: &str = "EQCariTouRoqxeqB9rVsXlSK5ML1XQW-QNxzzj4VXiQl9Vix"; + #[tokio::test] async fn test_swap_simulate_new_defaults_to_dex_v2() -> Result<()> { let params = SwapSimulateParams::new( @@ -26,6 +28,7 @@ async fn test_swap_simulate_new_defaults_to_dex_v2() -> Result<()> { } #[tokio::test] +#[ignore = "assets returns the full asset catalog; run explicitly when checking this heavy endpoint"] async fn test_assets() -> Result<()> { let client = init_env()?; let request = V1Request::Assets; @@ -124,6 +127,7 @@ async fn test_markets() -> Result<()> { } #[tokio::test] +#[ignore = "pools returns the full pool catalog; run explicitly when checking this heavy endpoint"] async fn test_pools() -> Result<()> { let client = init_env()?; let response = unwrap_response!(Pools, client.v1.exec(PoolsParams::default().with_dex_v2(false)).await?)?; @@ -200,6 +204,51 @@ async fn test_swap_simulate() -> Result<()> { Ok(()) } +#[tokio::test] +async fn test_liquidity_provision_simulate() -> Result<()> { + let client = init_env()?; + let response = unwrap_response!( + LiquidityProvisionSimulate, + client + .v1 + .exec( + LiquidityProvisionSimulateParams::new( + LiquidityProvisionType::Balanced, + "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c", + "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs", + "0.001", + ) + .with_pool_address("EQCGScrZe1xbyWqWDvdI6mzP-GAcAWFv6ZXuaJOuSqemxku4".to_string()) + .with_wallet_address(WALLET_ADDRESS_WITH_POSITIONS.to_string()) + .with_token_a_units("1000000000".to_string()), + ) + .await? + )?; + + assert_eq!(response.provision_type, LiquidityProvisionType::Balanced); + assert_eq!(response.token_a, "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"); + assert_eq!(response.token_b, "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs"); + Ok(()) +} + +#[tokio::test] +async fn test_jetton_wallet_address() -> Result<()> { + let client = init_env()?; + let response = unwrap_response!( + JettonWalletAddress, + client + .v1 + .exec(JettonWalletAddressParams::new( + "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs", + WALLET_ADDRESS_WITH_POSITIONS, + )) + .await? + )?; + + assert!(!response.address.is_empty()); + Ok(()) +} + #[tokio::test] async fn test_reverse_swap_simulate() -> Result<()> { let client = init_env()?; @@ -370,6 +419,128 @@ async fn test_stats_staking() -> Result<()> { Ok(()) } +#[tokio::test] +#[ignore = "wallet assets returns the full asset catalog; run explicitly when checking this heavy endpoint"] +async fn test_wallet_assets() -> Result<()> { + let client = init_env()?; + let response = + unwrap_response!(WalletAssets, client.v1.exec(WalletAssetsParams::new(WALLET_ADDRESS_WITH_POSITIONS)).await?)?; + assert_ne!(response.asset_list, vec![]); + assert!(response + .asset_list + .iter() + .any(|asset| asset.wallet_address.as_deref() == Some(WALLET_ADDRESS_WITH_POSITIONS))); + Ok(()) +} + +#[tokio::test] +async fn test_wallet_asset() -> Result<()> { + let client = init_env()?; + let response = unwrap_response!( + WalletAsset, + client + .v1 + .exec(WalletAssetParams::new( + WALLET_ADDRESS_WITH_POSITIONS, + "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c", + )) + .await? + )?; + assert_eq!(response.asset.wallet_address.as_deref(), Some(WALLET_ADDRESS_WITH_POSITIONS)); + Ok(()) +} + +#[tokio::test] +async fn test_wallet_pools() -> Result<()> { + let client = init_env()?; + let response = + unwrap_response!(WalletPools, client.v1.exec(WalletPoolsParams::new(WALLET_ADDRESS_WITH_POSITIONS)).await?)?; + assert_ne!(response.pool_list, vec![]); + Ok(()) +} + +#[tokio::test] +async fn test_wallet_pool() -> Result<()> { + let client = init_env()?; + let pool_address = "EQCGScrZe1xbyWqWDvdI6mzP-GAcAWFv6ZXuaJOuSqemxku4"; + let response = unwrap_response!( + WalletPool, + client.v1.exec(WalletPoolParams::new(WALLET_ADDRESS_WITH_POSITIONS, pool_address)).await? + )?; + assert_eq!(response.pool.address, pool_address); + Ok(()) +} + +#[tokio::test] +async fn test_wallet_farms() -> Result<()> { + let client = init_env()?; + let response = + unwrap_response!(WalletFarms, client.v1.exec(WalletFarmsParams::new(WALLET_ADDRESS_WITH_POSITIONS)).await?)?; + assert_ne!(response.farm_list, vec![]); + Ok(()) +} + +#[tokio::test] +async fn test_wallet_farm() -> Result<()> { + let client = init_env()?; + let farm_address = "EQD0eKCNL6SRy9ZOc8i6R7nvuIAcRs3BwREqq4Z4UzdXvnBz"; + let response = unwrap_response!( + WalletFarm, + client.v1.exec(WalletFarmParams::new(WALLET_ADDRESS_WITH_POSITIONS, farm_address)).await? + )?; + assert_eq!(response.farm.minter_address, farm_address); + Ok(()) +} + +#[tokio::test] +async fn test_wallet_stakes() -> Result<()> { + let client = init_env()?; + let response = + unwrap_response!(WalletStakes, client.v1.exec(WalletStakesParams::new(WALLET_ADDRESS_WITH_POSITIONS)).await?)?; + assert!(!response.ston_balance.is_empty()); + Ok(()) +} + +#[tokio::test] +async fn test_wallet_operations() -> Result<()> { + let client = init_env()?; + let response = unwrap_response!( + WalletOperations, + client + .v1 + .exec(WalletOperationsParams::new( + WALLET_ADDRESS_WITH_POSITIONS, + "2026-06-01T00:00:00", + "2026-06-02T00:00:00", + )) + .await? + )?; + assert!(response.operations.iter().all(|operation| !operation.operation.pool_address.is_empty())); + Ok(()) +} + +#[tokio::test] +async fn test_wallet_transactions_last() -> Result<()> { + let client = init_env()?; + let response = unwrap_response!( + WalletTransactionsLast, + client.v1.exec(WalletTransactionsLastParams::new(WALLET_ADDRESS_WITH_POSITIONS).with_limit(1)).await? + )?; + assert!(response.tx_list.len() <= 1); + Ok(()) +} + +#[tokio::test] +async fn test_wallet_fee_vaults() -> Result<()> { + let client = init_env()?; + let response = unwrap_response!( + WalletFeeVaults, + client.v1.exec(WalletFeeVaultsParams::new(WALLET_ADDRESS_WITH_POSITIONS)).await? + )?; + assert!(response.vault_list.iter().all(|vault| !vault.vault_address.is_empty())); + Ok(()) +} + #[tokio::test] async fn test_tx_action_tree_swap() -> Result<()> { let client = init_env()?; diff --git a/stonfi_api_client/tests/test_construction.rs b/stonfi_api_client/tests/test_construction.rs deleted file mode 100644 index 7199a95..0000000 --- a/stonfi_api_client/tests/test_construction.rs +++ /dev/null @@ -1,35 +0,0 @@ -use stonfi_api_client::v1::{ - ActionData, Asset, AssetsResponse, Blank, Pool, PoolsByMarketParams, SwapData, SwapSimulateResponse, TxId, -}; - -#[test] -fn test_request_params_support_default_setter_construction() { - let params = PoolsByMarketParams::default() - .with_asset0_address("asset-0".to_string()) - .with_asset1_address("asset-1".to_string()); - - assert_eq!(params.asset0_address, "asset-0"); - assert_eq!(params.asset1_address, "asset-1"); -} - -#[test] -fn test_response_models_support_default_setter_construction() { - let asset = Asset::default().with_contract_address("asset".to_string()).with_symbol("ASSET".to_string()); - let response = AssetsResponse::default().with_asset_list(vec![asset.clone()]); - - assert_eq!(asset.contract_address, "asset"); - assert_eq!(response.asset_list, vec![asset]); -} - -#[test] -fn test_action_models_support_default_setter_construction() { - let tx_id = TxId::default().with_hash("hash".to_string()); - let data = ActionData::::default().with_destination("destination".to_string()); - let pool = Pool::default().with_address("pool".to_string()); - let simulation = SwapSimulateResponse::default().with_pool_address("pool".to_string()); - - assert_eq!(tx_id.hash, "hash"); - assert_eq!(data.destination.as_deref(), Some("destination")); - assert_eq!(pool.address, "pool"); - assert_eq!(simulation.pool_address, "pool"); -} diff --git a/stonfi_api_client/tests/test_export.rs b/stonfi_api_client/tests/test_export.rs new file mode 100644 index 0000000..183a30c --- /dev/null +++ b/stonfi_api_client/tests/test_export.rs @@ -0,0 +1,80 @@ +use anyhow::Result; +use api_clients_core::Executor; +use std::sync::Arc; +use std::time::Duration; +use stonfi_api_client::api_client::StonfiApiClient; +use stonfi_api_client::api_client::DEFAULT_API_URL; +use stonfi_api_client::export::*; + +fn init_env() -> Result { + let _ = env_logger::builder().filter_level(log::LevelFilter::Debug).try_init(); + let executor = Executor::builder(DEFAULT_API_URL).with_timeout(Duration::from_secs(60)).build()?; + Ok(StonfiApiClient::builder().with_export_executor(Arc::new(executor)).build()?) +} + +#[tokio::test] +async fn test_export_cmc() -> Result<()> { + let client = init_env()?; + let response = client.export.exec(ExportRequest::Cmc).await?; + + match response { + ExportResponse::Cmc(stats) => assert!(!stats.is_empty()), + _ => anyhow::bail!("unexpected export response"), + } + Ok(()) +} + +#[tokio::test] +async fn test_export_dexscreener_latest_block() -> Result<()> { + let client = init_env()?; + let response = client.export.exec(ExportRequest::DexscreenerLatestBlock).await?; + + match response { + ExportResponse::DexscreenerLatestBlock(block) => assert!(block.block.block_number > 0), + _ => anyhow::bail!("unexpected export response"), + } + Ok(()) +} + +#[tokio::test] +async fn test_export_dexscreener_asset() -> Result<()> { + let client = init_env()?; + let address = "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c"; + let response = client.export.exec(ExportRequest::DexscreenerAsset(address.to_string())).await?; + + match response { + ExportResponse::DexscreenerAsset(asset) => assert_eq!(asset.asset.id, address), + _ => anyhow::bail!("unexpected export response"), + } + Ok(()) +} + +#[tokio::test] +async fn test_export_dexscreener_pair() -> Result<()> { + let client = init_env()?; + let address = "EQCGScrZe1xbyWqWDvdI6mzP-GAcAWFv6ZXuaJOuSqemxku4"; + let response = client.export.exec(ExportRequest::DexscreenerPair(address.to_string())).await?; + + match response { + ExportResponse::DexscreenerPair(pair) => assert_eq!(pair.pool.id, address), + _ => anyhow::bail!("unexpected export response"), + } + Ok(()) +} + +#[tokio::test] +async fn test_export_dexscreener_events() -> Result<()> { + let client = init_env()?; + let latest = match client.export.exec(ExportRequest::DexscreenerLatestBlock).await? { + ExportResponse::DexscreenerLatestBlock(block) => block.block.block_number, + _ => anyhow::bail!("unexpected export response"), + }; + + let response = client.export.exec(DexscreenerEventsParams::new(latest.saturating_sub(2), latest)).await?; + + match response { + ExportResponse::DexscreenerEvents(events) => assert!(events.events.len() <= 2_000), + _ => anyhow::bail!("unexpected export response"), + } + Ok(()) +} diff --git a/swap_coffee_api_client/README.md b/swap_coffee_api_client/README.md index 05356f2..1ac75cd 100644 --- a/swap_coffee_api_client/README.md +++ b/swap_coffee_api_client/README.md @@ -1,4 +1,40 @@ -### https://backend.swap.coffee/v1 +# swap_coffee_api_client + +Thin typed wrapper for the [Swap Coffee API v1](https://backend.swap.coffee/v1). + +Use this crate when an application needs typed access to Swap Coffee token or +pool data. The crate does not make routing decisions, execute swaps, or enforce +trade-safety policy; keep those decisions in the application layer. + +## Usage + +```toml +[dependencies] +swap_coffee_api_client = "0.1" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +``` + +Run requests inside an async Tokio runtime. Pass request parameter structs +directly where `Into` is implemented, and match response enums with a +wildcard arm. + +```rust,no_run +use swap_coffee_api_client::api_client::SwapCoffeeApiClient; +use swap_coffee_api_client::api_v1::{Dexes, V1Response}; + +# async fn example() -> Result<(), Box> { +let client = SwapCoffeeApiClient::builder().build()?; +let response = client.exec_api_v1(Dexes::new("coffee")).await?; + +match response { + V1Response::Pools(pages) => println!("pool pages: {}", pages.len()), + _ => println!("unexpected Swap Coffee response variant"), +} +# Ok(()) +# } +``` + +## Supported Endpoints | Method | Supported | |------------------------------------------|-----------| @@ -10,3 +46,6 @@ headroom. Build public POD structs with `Default::default().with_(...)` or request parameter constructors, pass request parameters directly to `exec_api_v1` where `Into` is implemented, and include a wildcard arm when matching response enums. + +Live API tests hit Swap Coffee directly. Pool counts, token ordering, metadata, +and liquidity fields can drift with upstream state. diff --git a/swap_coffee_api_client/src/lib.rs b/swap_coffee_api_client/src/lib.rs index f502b43..ad83b24 100644 --- a/swap_coffee_api_client/src/lib.rs +++ b/swap_coffee_api_client/src/lib.rs @@ -1,3 +1,5 @@ +#![doc = include_str!("../README.md")] + pub use api_clients_core; pub mod api_client; pub mod api_v1; diff --git a/tonco_api_client/README.md b/tonco_api_client/README.md index f71a6bf..32a869e 100644 --- a/tonco_api_client/README.md +++ b/tonco_api_client/README.md @@ -1,8 +1,85 @@ -### https://indexer.tonco.io/ +# tonco_api_client -This client provides graphql wrapper to access the [Tonco Indexer API](https://indexer.tonco.io/docs). +Low-level GraphQL wrapper for the [Tonco Indexer API](https://indexer.tonco.io/docs). -Check [tests](tests/test_graphql.rs) for usage examples. +Use this crate when an application needs to execute Tonco GraphQL queries while +keeping query files, generated query modules, schema refreshes, pagination, and +domain mapping in the application layer. The crate intentionally exposes +`exec_graphql(operation_name, query)` instead of higher-level pool or swap APIs. + +## Usage + +```toml +[dependencies] +tonco_api_client = "0.1" +serde = { version = "1", features = ["derive"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +``` + +Run requests inside an async Tokio runtime. `exec_graphql` sends the +`x-apollo-operation-name` header, unwraps GraphQL `data`, and maps GraphQL +`errors` or missing `data` to `ApiClientsError::UnexpectedResponse`. + +```rust,no_run +use serde::{Deserialize, Serialize}; +use tonco_api_client::api_client::ToncoApiClient; + +#[derive(Serialize)] +struct GraphqlRequest { + #[serde(rename = "operationName")] + operation_name: &'static str, + query: &'static str, + variables: V, +} + +#[derive(Serialize)] +struct EmptyVariables; + +#[derive(Deserialize)] +struct PoolsData { + pools: Option>>, +} + +#[derive(Deserialize)] +struct Pool { + address: String, +} + +# async fn example() -> Result<(), Box> { +let client = ToncoApiClient::builder().build()?; +let request = GraphqlRequest { + operation_name: "Pools", + query: "query Pools { pools { address } }", + variables: EmptyVariables, +}; + +let data: PoolsData = client.exec_graphql("Pools", &request).await?; +let pool_count = data.pools.unwrap_or_default().len(); +println!("pools: {pool_count}"); +# Ok(()) +# } +``` + +For larger applications, prefer generated query types from `graphql_client`. +Keep schema and query paths relative to the consuming application crate: + +```rust,ignore +use graphql_client::GraphQLQuery; + +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/graphql_schema.json", + query_path = "src/queries/pools.graphql", + response_derives = "Debug,Clone" +)] +pub struct Pools; +``` + +The checked-in `src/graphql_schema.json`, `tests/pools.graphql`, and +`tests/test_graphql.rs` files show the repository's live-test setup. Application +crates should own their own query files and schema update workflow. Public client types are marked `#[non_exhaustive]` where applicable for semver -headroom. +headroom. The default endpoint intentionally has no trailing slash: +`https://indexer.tonco.io`. Adding a trailing slash can produce `POST //` +against the live service. diff --git a/tonco_api_client/src/lib.rs b/tonco_api_client/src/lib.rs index 034ac08..d2f5ee9 100644 --- a/tonco_api_client/src/lib.rs +++ b/tonco_api_client/src/lib.rs @@ -1 +1,3 @@ +#![doc = include_str!("../README.md")] + pub mod api_client;