This guide covers common trading patterns using the O2 TypeScript SDK.
The O2 Exchange supports six order types, specified via the orderType
option of O2Client.createOrder.
A standard limit order that rests on the book if not immediately filled.
await client.createOrder("fFUEL/fUSDC", "buy", "0.02", "100");
Guaranteed to be a maker order. Rejected immediately if it would cross the spread and match an existing order.
await client.createOrder("fFUEL/fUSDC", "buy", "0.02", "100", { orderType: "PostOnly" });
Executes immediately at the best available price.
await client.createOrder("fFUEL/fUSDC", "buy", "0.03", "100", { orderType: "Market" });
Must be filled entirely in a single match, or the entire order is rejected.
await client.createOrder("fFUEL/fUSDC", "buy", "0.03", "100", { orderType: "FillOrKill" });
Like Spot, but includes a limit price and timestamp for time-in-force semantics.
Use the limitOrder() helper:
import { limitOrder } from "@o2exchange/sdk";
await client.createOrder(
"fFUEL/fUSDC", "buy", "0.02", "100",
{ orderType: limitOrder("0.025", String(Math.floor(Date.now() / 1000))) },
);
A market order with price bounds. Use the boundedMarketOrder() helper:
import { boundedMarketOrder } from "@o2exchange/sdk";
await client.createOrder(
"fFUEL/fUSDC", "buy", "0.025", "100",
{ orderType: boundedMarketOrder("0.03", "0.01") },
);
Optional parameters are passed via a CreateOrderOptions object:
import type { CreateOrderOptions } from "@o2exchange/sdk";
const opts: CreateOrderOptions = {
orderType: "PostOnly", // default: "Spot"
settleFirst: true, // default: true
collectOrders: true, // default: true
};
await client.createOrder("fFUEL/fUSDC", "buy", "0.02", "100", opts);
Price and quantity parameters accept a Numeric type (string | bigint):
string — Human-readable decimal (e.g., "0.02", "100.5"). The SDK
auto-scales to chain integers using precise string parsing (no float intermediary).bigint — Raw chain integer (e.g., 20000000n). Passed through directly
with no scaling.// Human-readable strings (auto-scaled):
await client.createOrder("fFUEL/fUSDC", "buy", "0.02", "100");
// Raw bigints (pass-through for power users):
await client.createOrder("fFUEL/fUSDC", "buy", 20000000n, 100000000000n);
// Mix modes:
await client.createOrder("fFUEL/fUSDC", "buy", "0.02", 100000000000n);
Values from API responses (e.g., order.price, depth.asks[0].price) are
bigint and pass through the bigint path automatically — no double-scaling.
Cancel an existing order:
await client.cancelOrder(orderId, "fFUEL/fUSDC");
// Cancel all open orders
await client.cancelAllOrders("fFUEL/fUSDC");
Use batchActions with the type-safe Action union for atomic
cancel+settle+replace patterns:
import {
cancelOrderAction,
createOrderAction,
settleBalanceAction,
type MarketActionGroup,
} from "@o2exchange/sdk";
const groups: MarketActionGroup[] = [
{
market: "fFUEL/fUSDC",
actions: [
cancelOrderAction(oldOrderId),
settleBalanceAction(),
createOrderAction("buy", "0.02", "100", "PostOnly"),
createOrderAction("sell", "0.05", "50", "PostOnly"),
],
},
];
const response = await client.batchActions(groups, true);
if (response.success) {
console.log(`TX: ${response.txId}`);
}
Market resolution, price/quantity scaling, FractionalPrice adjustment, min_order validation, and accounts registry lookup are all handled internally — no manual scaling or registry lookups needed.
When your orders are filled, the proceeds remain locked in the order book contract until they are settled back to your trading account.
createOrder handles this automatically when settleFirst is true
(the default). You can also settle manually:
await client.settleBalance("fFUEL/fUSDC");
A simple two-sided quoting loop using batch actions:
import {
O2Client, Network,
cancelOrderAction, createOrderAction, settleBalanceAction,
type MarketActionGroup, type OrderId,
} from "@o2exchange/sdk";
const client = new O2Client({ network: Network.TESTNET });
const wallet = O2Client.generateWallet();
await client.setupAccount(wallet);
await client.createSession(wallet, ["fFUEL/fUSDC"], 30);
let buyId: OrderId | null = null;
let sellId: OrderId | null = null;
while (true) {
const actions = [];
if (buyId) actions.push(cancelOrderAction(buyId));
if (sellId) actions.push(cancelOrderAction(sellId));
actions.push(settleBalanceAction());
actions.push(createOrderAction("buy", buyPrice, qty, "PostOnly"));
actions.push(createOrderAction("sell", sellPrice, qty, "PostOnly"));
const response = await client.batchActions(
[{ market: "fFUEL/fUSDC", actions }],
true,
);
buyId = response.orders?.find((o) => o.side === "buy")?.order_id ?? null;
sellId = response.orders?.find((o) => o.side === "sell")?.order_id ?? null;
await new Promise((r) => setTimeout(r, 10_000));
}
Query order status:
// All orders for an account
const orders = await client.getOrders(tradeAccountId, "fFUEL/fUSDC");
// Open orders only
const openOrders = await client.getOrders(tradeAccountId, "fFUEL/fUSDC", true);
// Single order by ID
const order = await client.getOrder("fFUEL/fUSDC", orderId);
console.log(`Closed: ${order.close}`);
console.log(`Filled: ${order.quantity_fill} / ${order.quantity}`); // bigint values
For real-time order updates, use streamOrders:
const stream = await client.streamOrders(tradeAccountId);
for await (const update of stream) {
for (const order of update.orders) {
console.log(`Order ${order.order_id}: ${order.close ? "closed" : "open"}`);
}
}
Withdraw funds from the trading account to the owner wallet:
const result = await client.withdraw(wallet, "fUSDC", "100.0");
console.log(`Withdrawal tx: ${result.tx_id}`);
The asset accepts symbol names ("fUSDC") or hex asset IDs. The amount
is Numeric — pass a human-readable string or raw bigint. The trade
account ID and destination are resolved from the wallet automatically.
Note: Withdrawals require the owner wallet (not the session key).
The SDK automatically manages nonces during trading. If you encounter nonce errors after a failed transaction, refresh the nonce:
await client.refreshNonce();
You can also fetch the current nonce directly:
const nonce = await client.getNonce(tradeAccountId);