Integrating DeepBook Margin
DeepBook Margin builds on the spot order book, but integrating it is not the same job as integrating spot. The structural differences an integrator meets fall into five areas: the object model, the isolated lending pools, the trading path, liquidation, and the events to index. For the objects in isolation see DeepBook Margin design; for a hands-on run see the Leveraged Position Workflow.
Margin trading borrows funds against collateral, and anyone can liquidate a borrowed position. Any position your integration opens is subject to the pool's risk parameters and permissionless liquidation. Read Margin Risks before building.
The margin object model
Spot trading uses a BalanceManager: it holds your balances, you hold its TradeCap, and you trade a Pool directly. A single BalanceManager works across every pool.
Margin wraps that. A MarginManager is a shared object that holds a BalanceManager inside itself, along with the caps that authorize action on it:
packages/deepbook_margin/sources/margin_manager.move. You probably need to run `pnpm prebuild` and restart the site.Two consequences drive the rest of integration:
- You never hold the wrapped manager's caps. The
deposit_cap,withdraw_cap, andtrade_caplive inside theMarginManager, which generates the trade proof that authorizes orders internally. So every deposit, withdraw, and trade goes through theMarginManager, not through theBalanceManagerAPI you would use for spot. - A manager is bound to one pool. The
deepbook_poolfield fixes the manager to a single DeepBook pool, andmargin_pool_idrecords the one margin pool a current loan is from. This is isolated margin, not a cross-pool account.
Isolated pools
Each borrowable asset has exactly one MarginPool, and the MarginRegistry enforces that uniqueness. A MarginManager borrows from one side of its pool at a time, base or quote, so risk stays on a single axis. A DeepBook pool is tradeable on margin only when the registry has enabled it and mapped its base and quote margin pools.
Before integrating against a pool, preflight it: confirm margin is enabled, resolve the two margin pool IDs a position can borrow from, and read the per-pool risk parameters. Do not assume a pool supports margin or hardcode its parameters.
import type { DeepBookMarginClient } from './client.js';
import { readRiskParams, type RiskParams } from './risk-params.js';
// Preflight a DeepBook pool before integrating margin against it. A pool is
// tradeable on margin only if the registry has enabled it, and each side (base,
// quote) borrows from a separate isolated MarginPool. This reports whether the
// pool is enabled, the two margin pool IDs a position can borrow from, and the
// per-pool risk parameters, so you can gate your integration on real on-chain
// state instead of assuming a pool supports margin.
export interface MarginPoolProfile {
poolKey: string;
marginEnabled: boolean;
baseMarginPoolId?: string;
quoteMarginPoolId?: string;
riskParams?: RiskParams;
}
export async function describeMarginPool(
client: DeepBookMarginClient,
poolKey: string,
): Promise<MarginPoolProfile> {
const db = client.deepbook;
const marginEnabled = await db.isPoolEnabledForMargin(poolKey);
if (!marginEnabled) return { poolKey, marginEnabled };
const [baseMarginPoolId, quoteMarginPoolId, riskParams] = await Promise.all([
db.getBaseMarginPoolId(poolKey),
db.getQuoteMarginPoolId(poolKey),
readRiskParams(client, poolKey),
]);
return { poolKey, marginEnabled, baseMarginPoolId, quoteMarginPoolId, riskParams };
}
Observe: for an enabled pool such as SUI_DBUSDC, this returns marginEnabled: true, a base and a quote margin pool ID, and the risk parameters. At the time of writing the risk parameters read as a liquidation risk ratio of 1.1, min borrow 1.2499, min withdraw 2, target 1.25, and rewards of 0.02 (user) and 0.03 (pool). Treat those as example values and read them onchain: they are set per pool by governance and are not the protocol defaults. See Risk ratio.
The trading path
Margin orders do not use the spot order entry. They go through the pool proxy, which places the order on the DeepBook pool on the manager's behalf and enforces the risk checks that keep a borrow safe. The current entry points are the _v2 functions (place_limit_order_v2, place_market_order_v2, and the reduce-only variants); the unsuffixed originals are deprecated, so integrate against the _v2 functions only.
Every pool proxy order, and every operation that values a position (borrow, withdraw, risk read), takes Pyth PriceInfoObject arguments and reverts on a price older than the pool's maximum age. Spot orders take no oracle. This is the largest day-to-day difference: a margin integration keeps price feeds fresh, and any valuing call fails if they are stale. See Oracle risk.
To monitor a position's health, read its oracle-priced risk ratio:
import type { DeepBookMarginClient } from './client.js';
import type { RiskParams } from './risk-params.js';
// Read your live risk ratio and see how close it is to liquidation.
// `getMarginManagerState` values your collateral and debt through the pool's
// Pyth oracles and returns the resulting risk ratio in one call, along with the
// oracle prices it used. Because it is oracle-priced, it moves whenever SUI
// moves, and it drifts down on its own as interest accrues on the debt.
export interface RiskStatus {
riskRatio: number;
baseDebt: string;
quoteDebt: string;
// Distance to the liquidation threshold, as a fraction of the current ratio.
// Small and shrinking is the danger sign.
marginToLiquidation: number;
liquidatable: boolean;
}
export async function readRiskStatus(
client: DeepBookMarginClient,
marginManagerKey: string,
params: RiskParams,
): Promise<RiskStatus> {
const state = await client.deepbook.getMarginManagerState(marginManagerKey);
const marginToLiquidation =
(state.riskRatio - params.liquidationRiskRatio) / state.riskRatio;
return {
riskRatio: state.riskRatio,
baseDebt: state.baseDebt,
quoteDebt: state.quoteDebt,
marginToLiquidation,
liquidatable: state.riskRatio <= params.liquidationRiskRatio,
};
}
Liquidation from the integrator's view
Anyone can liquidate any position your integration opens once its risk ratio falls to the pool's liquidation risk ratio. Liquidation is permissionless and, in the current deployment, runs through the separate margin_liquidation package: an entry callable by anyone checks eligibility with the registry, sources repay capital from a shared liquidation vault, and calls the manager's inner settlement to repay part of the debt and pay a reward out of the collateral, restoring the position to the target risk ratio. The Leveraged Position Workflow shows the onchain entry point, and Margin Risks works the consequences through with numbers.
For an integrator this means two things. First, no grace period protects the positions you build, so surface the risk ratio and its distance to the liquidation threshold to your users, and act early. Second, if you run a liquidator, it is a standard permissionless keeper: watch positions, supply oracles and repay capital, and receive the reward.
Events to index
Margin activity emits its own events, distinct from the spot order events. The DeepBook Margin Indexer exposes these through the public indexer; the names below are the onchain event structs to key on.
- Manager lifecycle (
margin_manager):MarginManagerCreatedEvent,DepositCollateralEvent,WithdrawCollateralEvent,LoanBorrowedEvent,LoanRepaidEvent,LiquidationEvent. - Liquidation vault (
margin_liquidation):LiquidationByVault. - Pool supply and borrow (
margin_pool):MarginPoolCreated,AssetSupplied,AssetWithdrawn, plus fee and referral events for suppliers. - Registry and governance (
margin_registry):DeepbookPoolRegistered,DeepbookPoolConfigUpdated,MaxPriceAgeUpdated,PriceToleranceUpdated,MinOpenRiskRatioUpdated, andCurrentPriceUpdated. Watch these if your integration caches per-pool risk parameters, because governance can change them.
Differences from the fully collateralized spot path
Every row below reflects current source or an existing reference page.
| Aspect | Spot (BalanceManager) | Margin (MarginManager) | Reference |
|---|---|---|---|
| Fund custody | Holds your balances directly | Wraps a BalanceManager inside the manager object | Object model |
| Trade authorization | You hold the TradeCap and trade the pool directly | The manager holds the deposit, withdraw, and trade caps; proofs are internal | Object model |
| Pool scope | One manager works across all pools | Bound to a single DeepBook pool | Design |
| Borrowing | None | Borrow one side from one isolated MarginPool at a time | Design |
| Order entry | placeLimitOrder on the pool | place_limit_order_v2 through the pool proxy, risk-checked | Trading path |
| Oracle dependency | None | Pyth prices required for borrow, withdraw, trade, and valuation | Oracle risk |
| Withdrawal | Withdraw settled funds anytime | Gated by the Min Withdraw Risk Ratio | Risk ratio |
| Cost | DEEP trading fee per fill | DEEP trading fee plus continuously accruing borrow interest | Interest rates |
| Liquidation | None | Permissionless at the liquidation threshold, routed through the liquidation vault | Margin Risks |
| New failure modes | Insufficient balance, off-tick or below min size | Adds risk-ratio aborts, stale-oracle reverts, and empty-pool borrow failures | Workflow troubleshooting |