Network: Mainnet▼
Implementing the o2 Client
The o2 client handles all blockchain interactions: session management, order placement, and market queries. This is the core integration layer between your trading agent and the o2 exchange.
Core Client Setup
The O2Client class wraps the o2 REST API and handles session-based authentication, making it easy to interact with the exchange.
Create the o2 client file:
bash
touch src/o2.tsAdd the following content to src/o2.ts:
typescript
// src/o2.ts
import { RestAPI } from '../lib/rest-api/client';
import { FuelSessionSigner } from '../lib/rest-api/signers/fuel-signer';
import { OrderType, OrderSide, MarketResponse as Market } from '../lib/rest-api/types';
import type { Account } from 'fuels';
import { MarketConfig } from './types/config';
import { Wallet, Provider } from 'fuels';
import pino from 'pino';
export class O2Client {
// REST API client for o2 exchange
private client: RestAPI;
// Fuel wallet for signing transactions
private wallet!: Wallet;
// Session signer for authentication
private signer!: FuelSessionSigner;
// Fuel network provider
private provider: Provider;
// Initialization flag to prevent double-initialization
private initialized: boolean = false;
// Logger instance
private logger: pino.Logger;
constructor(baseUrl: string, networkUrl: string, logger: pino.Logger) {
// Initialize REST API client with o2 base URL
this.client = new RestAPI({ basePath: baseUrl });
// Initialize Fuel provider with network URL
this.provider = new Provider(networkUrl);
this.logger = logger;
}
/**
* Initialize the O2 client with session credentials
* @param privateKey - Session private key for signing transactions
* @param contractId - Market contract ID to whitelist for this session
*/
async init(privateKey: string, contractId: string) {
if (!this.initialized) {
// Initialize the Fuel provider
await this.provider.init();
// Create wallet from session private key
this.wallet = Wallet.fromPrivateKey(privateKey, this.provider);
// Create session signer
this.signer = new FuelSessionSigner();
// Initialize trade account manager with session wallet
// This registers the session with the o2 API
await this.client.initTradeAccountManager({
account: this.wallet as Account,
signer: this.signer,
contractIds: [contractId], // Whitelist market contract
});
this.initialized = true;
}
}
/**
* Fetch market metadata from o2
* @param marketConfig - Market configuration with market_id
* @returns Market metadata including decimals and precision
*/
async getMarket(marketConfig: MarketConfig): Promise<Market | undefined> {
if (!this.initialized) {
this.logger.error('O2Client not initialized. Call init() before using this method.');
throw new Error('O2Client not initialized');
}
// Fetch all markets from o2 API
const markets: Market[] = (await (await this.client.getMarkets()).data()).markets;
// Find the market matching our config
return markets.find((m: Market) => m.market_id === marketConfig.market_id);
}
/**
* Place an order on o2
* @param market - Market metadata
* @param price - Order price (scaled blockchain integer)
* @param quantity - Order quantity (scaled blockchain integer)
* @param side - Order side (Buy or Sell)
* @param orderType - Order type (Spot or FillOrKill)
* @returns true if order was created successfully
*/
async placeOrder(
market: Market,
price: string,
quantity: string,
side: OrderSide,
orderType: OrderType
): Promise<boolean> {
if (!this.initialized) {
this.logger.error('O2Client not initialized. Call init() before using this method.');
throw new Error('O2Client not initialized');
}
// Submit transaction to o2 using session
const response = await this.client.sessionSubmitTransaction({
market,
actions: [
{
CreateOrder: {
side,
order_type: orderType,
price,
quantity,
},
},
],
});
// Extract order IDs from response
const orderIds = (await response.data()).orders.map((o: any) => o.order_id);
// Return true if at least one order was created
return orderIds.length > 0;
}
}Understanding Session-Based Trading
The initTradeAccountManager call establishes your session:
typescript
await this.client.initTradeAccountManager({
account: sessionWallet, // Temporary bot wallet
signer: this.signer, // Signs transactions
contractIds: [contractId], // Whitelisted contracts
});What Happens Behind the Scenes
- Session wallet address is registered with your trading account
- Session expiry is set (typically 30 days)
- Session is restricted to specific market contracts
- Session signature is stored in the o2 API
- All subsequent orders are signed by the session wallet
Security Benefits
- Trading agent only has session key, not owner key
- Session can be revoked at any time
- Session cannot withdraw funds (only trade)
- Session expires automatically (time-based)
- Contract whitelisting limits session scope
Market Metadata
The getMarket() method returns critical information:
typescript
{
market_id: "0x...",
contract_id: "0x...",
base: {
symbol: "ETH",
decimals: 9, // Scale up by 10^9
max_precision: 5 // Max 5 significant digits
},
quote: {
symbol: "USDC",
decimals: 9,
max_precision: 4
}
}This metadata is essential for:
- Scaling prices to blockchain integers
- Truncating precision to contract requirements
- Calculating order quantities correctly
Order Placement Flow
typescript
// 1. Get market metadata
const market = await o2Client.getMarket(config);
// 2. Calculate scaled price (see Math Utilities section)
const price = scaleUpAndTruncateToInt(
new Decimal(2500.50),
market.quote.decimals, // 9
market.quote.max_precision // 4
);
// Result: "2500000000000" (2500 * 10^9, truncated to 4 precision)
// 3. Calculate scaled quantity
const quantity = calculateBaseQuantity(
new Decimal(10), // $10 order size
new Decimal(2500), // Current price
market.base.decimals, // 9
market.base.max_precision // 5
);
// Result: "4000000" (0.004 ETH * 10^9)
// 4. Place order
await o2Client.placeOrder(market, price, quantity, OrderSide.Buy);