metaflux_client/lib.rs
1//! # metaflux-client — Rust SDK for the MetaFlux L1
2//!
3//! A typed client for the MetaFlux (MTF) derivatives L1. Every type, request
4//! shape, and channel discriminator follows the wire convention: snake_case
5//! JSON, plain-integer numerics (sizes / prices on fixed-point planes), and
6//! `market_id` rather than `coin`.
7//!
8//! The SDK signs and submits the node's full signed-action surface — perp and
9//! spot orders, TWAP, modify / batch, leverage and margin, vaults, staking,
10//! agent / account settings, and spot-margin / Earn — and reads market and
11//! account state over REST and WebSocket.
12//!
13//! ## Modules
14//!
15//! - [`wallet`] — secp256k1 keypair + EIP-712 signer (deterministic nonces).
16//! - [`rest`] — `/info`, `/exchange`, `/explorer` HTTP endpoints.
17//! - [`ws`] — WebSocket subscriptions, reconnect + heartbeat.
18//! - [`types`] — domain types shared by all transports.
19//! - [`faucet`] — devnet / testnet test-USDC faucet helper.
20//! - [`error`] — single [`ClientError`] thiserror enum.
21//!
22//! ## Quick start
23//!
24//! ```no_run
25//! use metaflux_client::{Client, wallet::Wallet};
26//!
27//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
28//! let wallet = Wallet::from_hex(&std::env::var("MTF_PRIVATE_KEY")?)?;
29//! let client = Client::new("https://devnet-gateway.mtf.exchange")?;
30//! let markets = client.rest().info().markets().await?;
31//! println!("{} markets available", markets.len());
32//! # let _ = wallet; Ok(())
33//! # }
34//! ```
35
36#![deny(missing_docs)]
37#![cfg_attr(docsrs, feature(doc_cfg))]
38
39pub mod error;
40pub mod faucet;
41pub mod mip3;
42pub mod rest;
43pub mod types;
44pub mod wallet;
45pub mod ws;
46
47pub use error::ClientError;
48pub use faucet::{FaucetResponse, request_faucet};
49pub use rest::RestClient;
50pub use types::{MarketId, OrderId, VaultId};
51pub use wallet::Wallet;
52
53/// Top-level convenience bundle.
54///
55/// Holds a single [`RestClient`] and a base URL re-used to construct WS
56/// clients on demand. The fields are owned and `Clone`able cheaply so a
57/// long-lived `Client` instance is the recommended pattern.
58#[derive(Debug, Clone)]
59pub struct Client {
60 base_url: String,
61 rest: RestClient,
62}
63
64impl Client {
65 /// Build a client targeting the given base URL.
66 ///
67 /// `base_url` should be of the form `https://devnet-gateway.mtf.exchange` (no trailing
68 /// path). The REST client will append `/info`, `/exchange`, etc.; the WS
69 /// client derives a `wss://` URL from this base.
70 ///
71 /// # Errors
72 ///
73 /// Returns [`ClientError::Builder`] if the URL is malformed or the
74 /// underlying `reqwest::Client` cannot be constructed.
75 pub fn new(base_url: impl Into<String>) -> Result<Self, ClientError> {
76 let base_url = base_url.into();
77 let rest = RestClient::new(&base_url)?;
78 Ok(Self { base_url, rest })
79 }
80
81 /// Access the REST sub-client.
82 #[must_use]
83 pub fn rest(&self) -> &RestClient {
84 &self.rest
85 }
86
87 /// The base URL this client targets.
88 #[must_use]
89 pub fn base_url(&self) -> &str {
90 &self.base_url
91 }
92
93 /// Convenience: REST `exchange` namespace.
94 ///
95 /// Equivalent to `self.rest().exchange()`.
96 #[must_use]
97 pub fn exchange(&self) -> rest::exchange::Exchange<'_> {
98 self.rest.exchange()
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn client_builds_with_valid_base_url() {
108 let c = Client::new("https://devnet-gateway.mtf.exchange").unwrap();
109 assert_eq!(c.base_url(), "https://devnet-gateway.mtf.exchange");
110 }
111}