Agara
SDKs

Rust

Install the Rust client, place an order with exact decimal amounts, and turn signing and streaming on or off with cargo features.

agara-sdk is an async client built for low latency. Responses are fully typed, identifiers are newtypes rather than bare strings, and amounts are exact decimals so nothing is rounded on the way to the wire. It requires a tokio runtime.

Like the Python client it concentrates on trading, and it additionally exposes LP rewards and rebate balances. It does not wrap market or event discovery, so find your token_id through Markets or the public API first.

Quickstart

Install the SDK

Not on crates.io yet, so depend on the public repository directly:

[dependencies]
agara-sdk = { git = "https://github.com/Mirai-Tech-Inc/agara-sdks", branch = "main" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
rust_decimal_macros = "1"

Once the crate is published that first dependency becomes agara-sdk = "<version>", and nothing else on this page changes. Pin to a commit rather than a branch for a reproducible build:

agara-sdk = { git = "https://github.com/Mirai-Tech-Inc/agara-sdks", rev = "<commit>" }

Choose your features

Default features pull in signing, which brings an Ethereum library for EIP-712, and streaming, which brings a WebSocket client. For a REST-only build that compiles faster and carries fewer dependencies, turn both off:

agara-sdk = { git = "https://github.com/Mirai-Tech-Inc/agara-sdks", branch = "main", default-features = false }

Create a client

Create a token first, following Authentication:

export AGARA_TOKEN="agt_..."
use agara_sdk::AgaraClient;

let client = AgaraClient::builder()
    .token(std::env::var("AGARA_TOKEN").expect("AGARA_TOKEN"))
    .base_url(std::env::var("AGARA_BASE_URL").expect("AGARA_BASE_URL"))
    .build()?;

Read a market and place an order

use agara_sdk::{ids::{Side, TokenId}, AgaraClient};
use rust_decimal_macros::dec;

#[tokio::main]
async fn main() -> agara_sdk::Result<()> {
    let client = AgaraClient::builder()
        .token(std::env::var("AGARA_TOKEN").expect("AGARA_TOKEN"))
        .build()?;

    let token = TokenId::new("21742...36455");

    let book = client.get_orderbook(&token).await?;
    println!("best bid {:?} / ask {:?}", book.best_bid(), book.best_ask());

    let accepted = client
        .place_limit_order()
        .token_id(token)
        .side(Side::Buy)
        .price(dec!(0.60))
        .shares(dec!(1))
        .call()
        .await?;

    let final_order = client.wait_for_terminal(&accepted.order_id).await?;
    println!("{:?}", final_order.status);

    Ok(())
}

Acceptance is not settlement. wait_for_terminal polls until the order reaches a terminal status, and that status still needs inspecting: a rejected or partially filled order is a normal terminal outcome, not a transport failure.

Amounts

Prices and sizes take rust_decimal::Decimal, so dec!(0.60) is exactly sixty cents rather than the nearest float. Conversion to the wire format happens inside the client, and a value finer than one micro unit is refused rather than rounded.

Accessors are explicit (as_decimal, as_str) instead of dereferencing, so a typed identifier cannot be passed where a plain string was meant. See Conventions for ticks and the minimum order value.

Pagination

List methods take a limit and an optional cursor, and return the cursor for the next page. Pass pagination.next_cursor back until it is empty:

let mut cursor = None;

loop {
    let page = client.list_trades(Some(500), cursor.clone()).await?;
    for trade in &page.trades {
        println!("{:?}", trade.fill_id);
    }

    cursor = page.pagination.next_cursor.clone();
    if cursor.is_none() {
        break;
    }
}

Keep that cursor to resume a scan later rather than replaying from the start.

Error handling

The error type carries the HTTP status and the server's structured problem information, with the recovery guidance already interpreted:

match client.cancel_all_orders().await {
    Ok(accepted) => println!("{accepted:?}"),
    Err(error) => {
        eprintln!("status {:?}", error.status_code());
        if error.is_retryable() {
            if let Some(after) = error.retry_after() {
                // the server asked you to wait this long
            }
        }
        return Err(error);
    }
}

is_retryable reflects what the server said, not a guess from the status code, so an unrecognised problem never authorises an automatic retry. A request that fails in transit may still have taken effect, so read the state back before sending it again. Errors has the code list.

Realtime subscriptions

With the streaming feature the client connects to the market and account channels over one typed WebSocket.

let mut stream = client.connect().await?;
stream.subscribe(/* channels */).await?;

while let Some(frame) = stream.next().await {
    println!("{frame:?}");
}

list_subscriptions reports what the server believes you are subscribed to, which is worth checking after a reconnect. Reconnection restores subscriptions but does not replay what you missed, so treat it as a prompt to take a fresh snapshot. Streaming covers the channels and Account stream the private one.

Next steps