ZetariumZetariumDex

Rust

Async signed-request helper plus a public WebSocket subscription.

Cargo dependencies

[dependencies]
reqwest    = { version = "0.12", features = ["json", "rustls-tls"] }
hmac       = "0.12"
sha2       = "0.10"
hex        = "0.4"
serde_json = "1"
tokio      = { version = "1", features = ["full"] }
urlencoding = "2"
uuid       = { version = "1", features = ["v4"] }
tokio-tungstenite = { version = "0.23", features = ["rustls-tls-webpki-roots"] }
futures-util = "0.3"

REST

// src/zdex.rs
use hmac::{Hmac, Mac};
use sha2::Sha256;
use serde_json::Value;
use std::time::{SystemTime, UNIX_EPOCH};

type HmacSha256 = Hmac<Sha256>;

const API_KEY: &str = "zd_84444a6e...";
const SECRET: &str = "073CuVWk...";
const BASE_URL: &str = "https://api.zetariumdex.com";

pub async fn signed_request(
    method: reqwest::Method,
    path: &str,
    params: &[(&str, &str)],
    body: Option<&Value>,
) -> reqwest::Result<Value> {
    let ts = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis()
        .to_string();

    let mut all: Vec<(&str, String)> =
        params.iter().map(|(k, v)| (*k, v.to_string())).collect();
    all.push(("timestamp", ts));
    all.sort_by(|a, b| a.0.cmp(b.0));

    let qs: String = all
        .iter()
        .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
        .collect::<Vec<_>>()
        .join("&");

    let mut mac = HmacSha256::new_from_slice(SECRET.as_bytes()).unwrap();
    mac.update(qs.as_bytes());
    let sig = hex::encode(mac.finalize().into_bytes());

    let url = format!("{BASE_URL}{path}?{qs}&signature={sig}");
    let mut req = reqwest::Client::new()
        .request(method, &url)
        .header("X-API-KEY", API_KEY);
    if let Some(b) = body {
        req = req.json(b);
    }
    req.send().await?.json().await
}

Usage

// src/main.rs
use serde_json::json;
use uuid::Uuid;

#[tokio::main]
async fn main() -> reqwest::Result<()> {
    // 1. Read futures balance
    let balance = signed_request(reqwest::Method::GET, "/v2/futures/balance", &[], None).await?;
    println!("{balance}");

    // 2. Idempotent LIMIT BUY
    let body = json!({
        "symbol": "BTCUSDT",
        "side": "BUY",
        "type": "LIMIT",
        "quantity": "0.001",
        "price": "30000",
        "clientOrderId": Uuid::new_v4().to_string(),
    });
    let order = signed_request(reqwest::Method::POST, "/v2/orders", &[], Some(&body)).await?;
    println!("{order}");

    Ok(())
}

WebSocket — public ticker

use futures_util::{SinkExt, StreamExt};
use tokio_tungstenite::tungstenite::Message;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (mut ws, _) =
        tokio_tungstenite::connect_async("wss://api.zetariumdex.com/ws").await?;

    ws.send(Message::Text(
        serde_json::json!({
            "action": "subscribe",
            "channel": "ticker",
            "symbol": "BTCUSDT",
        })
        .to_string(),
    ))
    .await?;

    while let Some(Ok(msg)) = ws.next().await {
        if let Message::Text(txt) = msg {
            println!("{txt}");
        }
    }
    Ok(())
}

On this page