Skip to content
🌐Network: Mainnet▼

Turbo Accounts API ​

Turbo Accounts use the same signed session-action API as regular o2 accounts, with additional actions for credit, debt, collateral, duration, and settlement.

For the product model and lifecycle, start with Turbo Accounts. This page is the raw API reference for building a Turbo integration.

Integration Model ​

A Turbo integration manages two contracts:

  • the parent trading account, which creates, opens, funds, and closes its Turbo children;
  • the Turbo Account, which places orders and performs draw, borrow, repay, collateral, and prolong actions.

All writes are signed session actions submitted through POST /v1/session/actions, except registration, which sends the same signed envelope to POST /v1/margin/accounts so the API can deploy and register the child.

The parent session scope must include every Turbo child it controls and the required pool, registry, and market contracts. Value withdrawn from a Turbo Account always returns to its parent.

Units and Rounding ​

  • Asset amounts are decimal strings in the asset's raw base units.
  • Prices are decimal strings scaled by 1e18.
  • Collateral-denominated results use the collateral asset's base units.
  • Owned value is rounded down.
  • Owed value is rounded up.

Do not calculate with JavaScript number. Use integers such as bigint and preserve the rounding direction shown in Calculations.

Read Endpoints ​

EndpointPurpose
GET /v1/accounts?owner={owner}List a user's parent and registered Turbo Accounts
GET /v1/margin/tiersGet the latest version of each available tier
GET /v1/margin/tiers/historyGet historical tier versions
GET /v1/margin/next-accountPredict a deterministic Turbo Account ID and registration configurables
GET /v1/margin/state?account={id}Get the complete state needed for valuation and limit calculations
GET /v1/margin/poolGet pool pause state and current per-asset inventory
GET /v1/margin/statisticsGet aggregate active-session and pool risk statistics
GET /v1/margin/activity?account={id}Get account lifecycle and margin activity
GET /v1/margin/close-cleanups?account={id}Get the order-book cleanup list required for a clean close
GET /v1/margin/discount?user={owner}Get the owner's current Turbo fee discounts

Lifecycle ​

1. Select a Tier ​

http
GET /v1/margin/tiers

A tier supplies the session's collateral requirement, k, duration, fees, supported assets and books, credit-line cap, threshold, open buffer, and price controls. Store the exact tier_id and version used by the session; tier terms can change between sessions.

Cohorts ​

A tier version sells one of two products, and the difference changes the open call, the liquidation rule, and settlement. The discriminator is the presence of a turbo object on the tier:

AspectLegacyTurbo (prepaid)
tier.turboKey absentPresent
Collateral postedThe trader's, refundableNone. The session opens on zero collateral
Entry paymentA deposit plus feesA price, forfeited at settlement
Liquidation ruleV <= tier.thresholdAn anchored floor. See Health, Freeze, and Liquidation
Extra termsPurchased with ProlongSessionEarned free by hitting a profit target, on tiers with max_rollovers > 0

The key is omitted rather than nulled, so test for presence. A live session also carries session.cohort, either "Legacy" or "Turbo", pinned at open and stable for the session's life. Prefer session.cohort when a session exists and fall back to the tier test when it does not.

tier.turbo contains:

FieldMeaning
forfeit_collateralSettlement routes the posted collateral to the platform rather than back to the parent
max_loss_bpsFour-entry drawdown escalator, bps of tier.line, indexed by rollovers_used and clamped at the last entry
rollover_profit_bpsProfit above the anchor that earns one free term, bps of tier.line
max_rolloversHow many free terms can be earned
term_secondsHow long one term runs. Authoritative on a prepaid tier; do not derive it from duration + prolong_seconds[period]
rollover_window_secondsVestigial. Nothing schedules against it, and there is no early claim. Do not build a claim window from it

An all-zero max_loss_bps means the tier sells no anchored drawdown, and the static tier.threshold stands alone.

A tier without an expiry publishes duration and turbo.term_seconds as 18446744073709551615 (u64::MAX), and its sessions carry the same value in expires_at. Treat it as "no expiry": do not add it to a timestamp or render it as a countdown. A tier with max_rollovers: 0 sells no free terms, so every max_loss_bps entry is equal, the floor never moves, and GET /v1/margin/state omits the rollover object. The tiers on sale today are of this kind; read them from GET /v1/margin/tiers rather than assuming it.

2. Predict and Register a Turbo Account ​

Predict the next child of a parent trading account:

http
GET /v1/margin/next-account?parent_contract=0xPARENT

The response contains the deterministic contract_id, its index, and the configurables covered by registration.

Register that child using the standard signed session envelope with this action:

http
POST /v1/margin/accounts
json
{
  "RegisterMarginAccount": {
    "margin_account_id": "0xTURBO_ACCOUNT",
    "index": "0"
  }
}

The trade_account_id in the envelope is the parent account. See session action request structure for the signature, nonce, session identity, and market grouping.

3. Start a Session ​

Submit StartMarginSession from the parent account:

json
{
  "StartMarginSession": {
    "margin_account_id": "0xTURBO_ACCOUNT",
    "tier_id": "12",
    "amount": "250000000",
    "period": "Week"
  }
}

amount is the value forwarded with the call. period is one of SixHours, Day, Week, or Month. Starting a session purchases the base duration plus the selected initial period and charges the applicable opening and period fees.

What amount has to be depends on the cohort:

amount=discounted(required_collateral)+discounted(open_fee+prolong_fee[period])

On a legacy tier the discount reaches the fee leg only. required_collateral is the trader's own returnable margin, so discounting it would under-fund the session below the tier's floor and the open reverts.

On a prepaid tier the discount reaches both legs. The entry is a price rather than a deposit, and the session opens on zero collateral.

Discount each leg separately and then sum, mirroring the chain: discounted_price truncates, so discounting the total instead can differ by one base unit. A prepaid open requires an exact amount and is refused rather than clamped, which makes that one unit a revert.

A prepaid tier sells one term flat and refuses every period but Week at open.

4. Read or Subscribe to State ​

http
GET /v1/margin/state?account=0xTURBO_ACCOUNT

The response contains:

  • session: collateral, credit line, quote drawn, in-kind debts, fees, timestamps, and cohort;
  • tier: the exact tier version used by the active session, including its turbo overlay if it has one;
  • rollover: the prepaid session's rollover position, described below;
  • balances: on_account, received, locked, settled, and debt for each asset;
  • prices: bid, ask, decimals, timestamp, and stale status;
  • collateral_decimals: the result scale for collateral-denominated formulas;
  • discount: the parent's active fee-discount state.

Use this snapshot as one internally consistent input to the formulas below. Do not mix balances, prices, and session fields fetched at different times.

The rollover object ​

Emitted only when the tier's terms enable a rollover and an anchor was seeded, so it is absent on every legacy session and on a forfeit-only prepaid tier.

FieldMeaning
anchorV at the current term's start, raw collateral units. Account value, not equity: equity = V - k, so comparing the two directly is off by k
withdrawnCollateral value withdrawn this term. Added back when judging the profit bar, so banked profit still counts. Resets on rollover
outsideValue that arrived this term rather than being earned. Subtracted when judging the bar, which is what stops a transfer in from buying a free term
rollovers_usedWhich term the session is in, and therefore which max_loss_bps entry applies
max_rolloversHow many free terms this session can earn
profit_requiredThe absolute profit above anchor one rollover demands, computed as line * rollover_profit_bps / 10_000
eligible_nowThe backend's hint, from its own marked valuation. The chain re-verifies regardless

anchor is fixed for the term. A withdrawal moves withdrawn, never the anchor.

5. Trade and Manage Credit ​

Regular CreateOrder, CancelOrder, and SettleBalance actions work on a Turbo Account. Submit them with the Turbo Account as trade_account_id.

A tier whose books are Turbo order books trades with "turbo": true on each market group. Match tier.books against GET /v1/markets?turbo=true to tell which kind a tier uses. See Turbo Order Books.

Turbo Account actions are:

ActionPurpose
DrawDraw the pool's collateral asset against the credit line
ReturnQuoteReturn previously drawn quote held by the account
BorrowBorrow a supported non-collateral asset in kind
RepayRepay an in-kind debt with that asset
RepayFromCollateralReduce drawn quote directly against posted collateral
RepayBaseFromCollateralBuy out an in-kind debt from posted collateral at the ask plus fee
AddCollateralConvert collateral-asset coins already on the Turbo Account into posted collateral
WithdrawFromMarginWithdraw eligible active-session funds to the parent
ProlongSessionPurchase one or more additional periods
SetAutoProlongEnable or disable automatic prolongation

Draw, ReturnQuote, Borrow and Repay behave identically in both cohorts. The three collateral actions and ProlongSession are legacy remedies: a prepaid session posts no collateral to repay from, and it gains extra terms by clearing rollover.profit_required rather than by buying them. A prepaid term is rolled at its deadline through the cosigned expire_or_rollover, not by a client action.

Example action fragments:

json
{ "Draw": { "amount": "1000000000" } }
json
{
  "Borrow": {
    "asset_id": "0xASSET",
    "amount": "100000000"
  }
}
json
{
  "Repay": {
    "asset_id": "0xASSET",
    "amount": "100000000"
  }
}
json
{
  "ProlongSession": {
    "period": "Day",
    "times": "1"
  }
}
json
{ "SetAutoProlong": { "period": null } }

Margin actions share the request's market-grouped action list. Their market_id positions the action in the batch; the action targets the pool rather than that order book. This lets a client atomically return quote before borrowing, or draw quote before creating an order.

6. Close the Session ​

Before a clean close, cancel open orders, settle book balances, return quote, and repay every in-kind debt. Then fetch the current cleanup list:

http
GET /v1/margin/close-cleanups?account=0xTURBO_ACCOUNT

Submit CloseMarginSession from the parent account:

json
{
  "CloseMarginSession": {
    "margin_account_id": "0xTURBO_ACCOUNT",
    "cleanups": []
  }
}

Pass the exact cleanups returned by the endpoint. A stale or incomplete list reverts rather than leaving funds on a book.

With no active session, the parent can move a remaining child balance back using WithdrawFromMarginAccount.

Settlement ​

Settlement terms depend on the cohort, and every figure below is published per tier rather than fixed by the product. Read them from GET /v1/margin/tiers.

AspectLegacyTurbo (prepaid)
Liquidation leveltier.thresholdThe anchored floor, below
Posted collateral at settlementReturned to the parentForfeited to the platform (turbo.forfeit_collateral)
Trader's share of realized profit10_000 - tier.profit_share_bps, in bpsThe same field. Zero share on the shipped prepaid tiers, so the trader keeps all of it
Additional termsBought with ProlongSessionEarned free by clearing profit_required

At expiry or liquidation, positions are unwound off market to avoid slippage on the o2 order book. The amount returned to the parent account may include supported assets rather than only USDC.

A prepaid entry is a price and does not pro-rate, so closing early neither reduces nor refunds it.

The last escalator step is wider than the deposit, so a final-term floor can legitimately land below the pool's own principal k. On a 10,000 line that is a floor of 9,700 against a k of 9,750. Settlement books that tail as bad debt deliberately. Do not clamp the floor at k or at tier.threshold: clamping makes distance-to-liquidation read wrong for exactly the sessions that earned the room.

Turbo Order Books ​

Each market can have a Turbo order book paired with its public (canonical) order book. Turbo orders match only against shared liquidity that the house market maker and whitelisted market makers post through the canonical book. They never match ordinary public orders, and public takers never match resting Turbo orders.

Selecting a Turbo book ​

Add turbo=true to a market-scoped request to read the Turbo book instead of the canonical one. The market_id stays the same.

EndpointWith turbo=true
GET /v1/marketsLists only markets that have a Turbo book, rewritten for it (below)
GET /v1/markets/summary, GET /v1/markets/ticker, GET /v1/markets/feesComputed from the Turbo book. The ticker's bid and ask are the best executable shared levels. Markets without a Turbo book are skipped
GET /v1/depthExecutable or demand depth. See Turbo depth views
GET /v1/barsPrices from the Turbo book. History before the Turbo book was attached is inherited from the canonical book
GET /v1/trades, GET /v1/trades_by_account, GET /v1/trade_by_account, GET /v1/fee_for_tradeTrades and fees on the Turbo book
GET /v1/order, GET /v1/orders, GET /v1/orders/book, GET /v1/orders/activeOrders on the Turbo book

turbo defaults to false, and a canonical response is unchanged. A Turbo response adds "turbo": true. Requesting turbo=true for a market with no Turbo book returns MarketNotFound with the message Market {market_id} has no Turbo order book.

Account-wide endpoints (/v1/balance, /v1/accounts, /v1/margin/*, /v1/events) do not take turbo.

Turbo market rows ​

GET /v1/markets?turbo=true returns each market's canonical row with these changes:

FieldValue
contract_idThe Turbo book's contract ID. Sign Turbo orders against this contract
canonical_contract_idThe paired canonical book
turbotrue
connectedWhether the Turbo book is connected to its canonical book. Creates are refused while it is disconnected
pausedtrue if either the Turbo book or its canonical book is paused
maker_fee, taker_feeThe Turbo book's own rates, in ppm (100 is 0.01%)
min_order, dust, price_window, trigger_max_slippage, asset decimals and precisionThe Turbo book's own configuration

Placing orders ​

In POST /v1/session/actions, set turbo on each market group that targets a Turbo book:

json
{
  "actions": [
    {
      "market_id": "0xMARKET_ID",
      "turbo": true,
      "actions": [{ "CreateOrder": { "...": "..." } }]
    }
  ],
  "signature": "...",
  "trade_account_id": "0xTURBO_ACCOUNT"
}

CreateOrder, CreateOrderWithTriggers, CreateTriggerOrder(s), CancelOrder, CancelTriggerOrder and SettleBalance work as they do on a canonical book. Creates are refused with market with id {market_id} is paused if either book is paused, and with House book is disconnected while the Turbo book is disconnected. Each returned order carries "turbo": true when it sits on a Turbo book.

In POST /v1/accounts/actions, SettleBalance and Whitelist take the same optional turbo flag to target the Turbo book.

How Turbo orders fill ​

OrderFills against
Turbo market or crossing limit orderShared quotes resting on the canonical book
Resting Turbo orderA shared quote placed at or through its price, or an explicit house execution

A trade filled from shared liquidity carries a shared object, { "funding_source": { "order_id": "0x..." } }, naming the canonical order that funded it. It appears on REST and WebSocket trade responses.

Market maker actions ​

Shared liquidity is posted with two session actions, available only to trading accounts that hold the house role on the market's Turbo book. Both are sent on the canonical market group ("turbo": false); sending them with "turbo": true is refused with House posts and selects its liquidity through the canonical book.

ActionFieldsPurpose
CreateSharedOrderside, price, quantity, order_type ("Spot" or "PostOnly", default "PostOnly")Post a quote on the canonical book that Turbo takers can also fill. A Spot order first fills any resting Turbo orders it crosses
ExecuteTurboOrderssource_order_id, max_base_quantity, max_fillsFill resting Turbo orders from an existing shared quote. Bounds must be positive

Without a paired Turbo book, a shared order is placed as an ordinary order of the same type. Market makers interested in providing Turbo liquidity can contact the o2 team.

WebSocket ​

RequestTurbo fields
subscribe_depth, subscribe_depth_view and their unsubscribesturbo: true, and optional view ("executable" or "demand")
subscribe_trades, unsubscribe_tradesturbo: true
subscribe_orders, unsubscribe_ordersturbo: true. Turbo and canonical order subscriptions are separate and can run together; unsubscribe with the same flag

Turbo trade messages add turbo: true, confirmed and indexed_height. If the Turbo trade stream has to restart, it sends { "action": "error", "turbo": true, "resync_required": true, "message": "..." } and closes the subscription; resubscribe to continue. subscribe_balances, subscribe_nonce and subscribe_margin are account-wide and ignore turbo.

Events ​

GET /v1/events reports Turbo book activity with these event types:

EventFields
SharedExecutionhouse_order_id, source_order_id, turbo_order_id, house_trader, turbo_trader, turbo_side, house_execution_role (Maker or Taker), quantity, price, house_fee_amount, turbo_fee_amount, timestamp
SharedTradePerformedsource_order_id, turbo_order_id, house_execution_role, quantity, price, timestamp
HouseOrderCreatedcanonical_book, order_id, source_order_id, trader, side, price, quantity, timestamp
HouseOrderCompletedorder_id, timestamp
SidecarChangedprevious, current: the Turbo book paired with a canonical book, before and after

Calculations ​

The raw API returns inputs rather than precomputed action limits. A custom client must reproduce these calculations before presenting or signing a value.

The formulas are shown in calculation order. Each display is one value a client can compute and inspect independently.

Symbols ​

SymbolDefinition
cdCollateral asset decimals
ad[a]Decimals of asset a
bid[a], ask[a]1e18-scaled prices for asset a
kTier capital constant
CPosted collateral, including fees charged from it
FFees accrued for the active session
LCurrent credit line
D_qQuote currently drawn
debt[a]In-kind debt in asset a
H[a]Total holdings of asset a
VConservatively marked account value

Price Conversion ​

First compute the scale for asset a:

scale(a)=1018+ad[a]−min(cd,18)

Round down when valuing something the account owns:

ownedValue(q,p,a)=⌊q⋅pscale(a)⌋

Round up when valuing something the account owes:

owedValue(q,p,a)=⌈q⋅pscale(a)⌉

The ceiling operation is:

⌈xy⌉={0x=0⌊x+y−1y⌋x>0

Credit Line ​

The tier first sets the maximum possible line:

line cap=⌊tier.line⋅tier.max_credit_line_bps10,000⌋

Fees reduce the collateral that contributes to the line:

net collateral=C−F

The current credit line is the lower of the collateral-derived line and the tier cap:

L=min(k+net collateral, line cap)

Additional collateral that can still increase the line is:

collateral headroom=max(0, line cap−k−max(0, net collateral))

A prepaid session holds C = 0, so its line is min(k, line cap) and the collateral-denominated remedies have nothing to draw on. AddCollateral is still accepted, and value added that way counts toward rollover.outside rather than toward the profit bar.

Holdings and Net Position ​

For each asset:

H[a]=onAccount[a]+received[a]+locked[a]+settled[a]

Net holdings against debt before applying a price:

pos[a]=H[a]−debt[a]

For the collateral asset, its value calculation also includes:

undrawn line=L−Dq

Collateral posted above the amount converted into credit remains part of value:

equity above cap=max(0,C+k−L)

Account Value, Equity, and PnL ​

Mark a positive position at the bid and a negative position at the ask:

mark(pos[a])={ownedValue(pos[a],bid[a],a)pos[a]≥0−owedValue(|pos[a]|,ask[a],a)pos[a]<0

Then:

V=H[collateral]+(L−Dq)+max(0,C+k−L)+∑a≠collateralmark(pos[a])−F

Remove the tier's capital to get the trader's equity:

trader equity=V−k

Fees are removed from posted collateral to establish the trading basis:

trading basis=C−F

Trading PnL compares equity with that basis:

trading PnL=trader equity−trading basis

Draws and borrows are balance-sheet movements, not PnL. Fees are reported separately because they reduce both equity and trading basis.

Loan Cap and Draw Capacity ​

Value every in-kind debt at the ask and round up:

in-kind debt value=∑aowedValue(debt[a],ask[a],a)

Add quote already drawn:

outstanding=Dq+in-kind debt value

The unused part of the line is:

loan headroom=max(0,L−outstanding)

Quote inventory can further restrict a draw:

drawable quote=min(loan headroom, pool quote inventory)

The protocol requires outstanding <= L after the action batch.

Borrow Capacity ​

For target asset a:

1. Quote That Can Be Returned First ​

returnable quote=min(Dq, on-account quote, spendable cash)

returnable quote supports a batch that returns quote before borrowing. Without that leading action, use only the current loan headroom.

2. Limit Imposed by the Loan Cap ​

by loan cap[a]=⌊(loan headroom+returnable quote)scale(a)ask[a]⌋

3. Stress the Ask Price ​

shocked ask[a]=⌊ask[a](10,000+stress band bps)10,000⌋

4. Limit Imposed by Exposure ​

by exposure[a]=⌊exposure headroomscale(a)shocked ask[a]⌋

5. Final Borrow Limit ​

Take the smallest of the two risk limits and the asset inventory:

borrowable[a]=max(0,min(by loan cap[a],by exposure[a],pool inventory[a]))

The collateral asset cannot be borrowed in kind; use Draw.

Reserve and Spendable Quote ​

Cash needed to buy back open net shorts is reserved:

reserve=∑a:pos[a]<0owedValue(|pos[a]|,ask[a],a)

Count quote held directly or settled on a book:

cash held=on-account quote+settled quote

Only quote above the reserve is spendable:

spendable cash=max(0,cash held−reserve)

The on-chain calculation may reduce the reserve by quote already committed to a resting buyback on the same book. A client that cannot attribute locked quote to a specific book should omit that credit; this is conservative.

Gross Exposure ​

Gross exposure includes current positions, quote locked by buy orders, and the possible filled state of resting sell orders. Let:

effective position[a]=pos[a]−locked sell quantity[a]

For each spot and stressed price pass, the non-collateral contribution is the larger absolute value of the current and effective positions. Add quote locked by buys at par:

G(pass)=quote locked by buys+∑amax(|mark(pos[a])|,|mark(effectivePos[a])|)

Stress moves prices against the position by the configured stress band. Longs use a lower stressed bid; shorts use a higher stressed ask.

Use the largest result from the spot and stressed passes:

gross exposure=max(Gspot,Gstressed-marked,Gstressed-filled)

The remaining exposure capacity is:

exposure headroom=max(0,L−gross exposure)

Health, Freeze, and Liquidation ​

The threshold itself depends on the cohort. A legacy session uses the tier's static value; a prepaid session replaces it with an anchored floor plus the cost of unwinding what the account currently holds.

Legacy:

threshold=tier.threshold

Prepaid. First the loss floor, from the escalator entry for the current term:

floor=max(0, tier.line−⌊tier.line⋅max_loss_bps[rollovers_used]10,000⌋)

The allowance rounds down, so a fraction of a unit tightens the floor.

The drawdown is measured on tier.line, a constant, not on the anchor. The anchor served as both baselines until 0.4.1-rc, and because withdraw decremented it, a trader who made his week and withdrew it sat back at the line while his floor had dropped by the same amount. Repeating a donate-and-withdraw cycle walked the floor toward zero. The two baselines are now separate: anchor carries the rollover bar alone, and this floor reads the line, which nothing the session does can move. rollovers_used is read only to index the escalator.

Then add the absorption cost, which is what unwinding the current basket would cost the pool:

threshold=floor+absorption cost

Note that the prepaid rule adds the absorption cost rather than taking a maximum with it, so the effective floor rises with position size.

This threshold replaces the static one. Do not take a maximum of the two.

Liquidation becomes permissionless at whichever threshold applies:

liquidatable⟺V≤threshold

The maximum value an action may concede while remaining strictly above that line is:

giveaway allowance=max(0,V−threshold−1)

Use the worse of the two stressed values:

stressed equity=min(Vstressed-marked,Vstressed-filled)−k

Risk-increasing actions freeze below the freeze line, which is also cohort-dependent:

freeze line={k+tier.open_bufferlegacyfloor+(tier.open_buffer−tier.maintenance)+absorption coststressedprepaidfrozen⟺stressed equity<freeze line−k

Under the prepaid regime the freeze line sits one open_buffer - maintenance gap above whatever the threshold turned out to be, which is what keeps freeze > threshold true in both regimes and at every price. The absorption cost must be added to both lines: adding it to the threshold alone inverts the ordering.

When frozen, a batch must not increase exposure. Reducing risk can still be accepted. The one-unit subtraction in giveaway allowance preserves the strict safe side because liquidation begins at equality.

Buy-Side Capacity ​

A buy must leave enough quote reserved for shorts and stay inside the exposure line:

buy capacity=min(spendable cash+drawable quote, exposure headroom)

This is still subject to price freshness, custody, the giveaway allowance, order bounds, market support, and the batch-level stressed checks.

Stream State with subscribe_margin ​

Connect to the o2 WebSocket and subscribe to one Turbo Account:

json
{
  "action": "subscribe_margin",
  "margin_account_id": "0xTURBO_ACCOUNT"
}

The first message is a complete state snapshot:

json
{
  "action": "subscribe_margin",
  "state": {}
}

Subsequent messages carry changes:

json
{
  "action": "subscribe_margin_update",
  "event": {},
  "onchain_timestamp": 0,
  "seen_timestamp": 0
}

Updates include activity, prices, tier publication, pause state, balances, and session closure. Activity fields such as drawn_total, debt_total, and received_total are post-event totals: replace the local value rather than adding the event amount again.

Start from the snapshot, fold updates in order, and run the formulas locally after each relevant change. On reconnect, a sequence gap, or a failed fold, fetch a fresh GET /v1/margin/state snapshot before signing another action.

To unsubscribe:

json
{ "action": "unsubscribe_margin" }

Raw API vs SDK ​

The raw API intentionally exposes state and protocol inputs. Raw clients are responsible for integer conversion, rounding, action ordering, price freshness, and every applicable risk limit.

Turbo is supported in the TypeScript SDK, which composes these calls for you. long() and short() build the sweep, the funding leg and the order into one signed batch, so you never assemble a draw or a borrow by hand.

Requires @o2exchange/sdk 0.4.0 or later

Turbo is not in 0.3.0. If client.turbo is undefined, you are on an older release — upgrade, or install from main in the SDK repository until 0.4.0 is published.

typescript
import { Network, O2Client, boundedMarketFromSlippage } from "@o2exchange/sdk";

const client = new O2Client({ network: Network.MAINNET });
const wallet = O2Client.loadWallet(process.env.PRIVATE_KEY!);
await client.setupAccount(wallet);

// `turbo: true` also scopes the session to the margin contracts. That
// scope is signed and cannot be widened afterwards, so a session created
// without it can do nothing with a Turbo account.
await client.createSession(wallet, ["ETH/USDC"], { turbo: true });

const tier = await client.turbo.cheapestTier();
await client.turbo.open({
  tierId: tier.tier_id,
  collateral: client.turbo.openingCost(tier), // margin plus the premium
});

await client.turbo.long("ETH/USDC", { notional: "2000" });
await client.turbo.closePosition("ETH/USDC");

Two rules the SDK enforces that a raw client must handle itself:

  • Market orders must be bounded. The pool funds an order at its worst-case execution price, and an unbounded market order has none, so it is rejected. Pass boundedMarketFromSlippage(rawPrice, 100, tick) for a 1% band.
  • Attached take-profit and stop-loss inherit the position's size. The contract accepts nothing else for a protective leg created alongside its parent order.

The Python and Rust SDKs do not cover Turbo yet; use the raw API from those languages.

For the full surface — tiers, positions, risk limits, take-profit and stop-loss, extensions, referrals and closing — see the Turbo guide and the SDK documentation on GitHub.

Using the raw API directly, your integration is responsible for integer conversion, rounding, state updates, action ordering, and risk calculations. Treat an API rejection as authoritative if local state is stale.