Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Accounts & Portfolios

This page covers reading a trader’s state: on one market (accountSnapshot) and across every market where the account holds anything (portfolio). A snapshot has the position and its health at the live price, the live orders, what can be withdrawn now, claimable fees, the meta-transaction nonce, the terminal payout of a market that is winding down, and the wallet’s USDC balance and allowance.

The samples assume bz connected as in Connecting with a price source, user the trader’s address and eth a market from Markets.

One Market

const me = await bz.account.accountSnapshot(eth, user);
console.log(`valued at ${me.price ? formatUnits18(me.price.spot, 2) : "no price"} (${me.price?.source ?? "-"})`);
if (me.hasPosition && me.health) {
  const h = me.health;
  console.log(`${me.position.isLong ? "long" : "short"} ${formatUnits18(me.position.size)} from ${formatUnits18(me.position.entryPrice, 2)}`);
  console.log(`equity ${formatUnits18(h.equity, 2)}, PnL ${formatUnits18(h.unrealizedPnl, 2)} + funding ${formatUnits18(h.fundingPnl, 2)}`);
  console.log(`margin ratio ${h.marginRatio.toFixed(2)} (liquidatable below 1), leverage ${h.leverage?.toFixed(1) ?? "-"}x`);
}

bz.account.accountSnapshot(market, user, options?) reads the account in two batched calls: first the position, its live order ids, the pair’s stored price, the claimable fees and the wallet’s USDC balance; then everything that depends on those (the lens’s solvency and withdrawal figures at the account’s price, the order exposure, the nonce, the terminal entitlement, the allowance, and the orders themselves), sent together with the market’s state read.

The position is valued at the first price available, in this order:

  1. options.price, a price you already hold ({ spot, low?, high?, publishTime? });
  2. the live price from options.prices, a LivePrices stream;
  3. one Pyth Pro request for the market’s feeds, when the connection’s price source is a proPriceSource (and fetchPrices is not false);
  4. the pair’s stored price.

snapshot.price.source says which: "given", "live", "pro" or "stored".

OptionDefaultMeaning
blockthe latest{ number, timestamp } to read at.
pricesnoneLive prices to value the position at and to judge the venue with.
fetchPricestrueWithout live prices, ask the connection’s Pyth Pro source for the feeds, in parallel with the first read.
pricenoneA price you hold, used instead of a live one.
marketnoneThe market’s snapshot (or state read) at the same block, to skip reading it again. Its block is then the snapshot’s.

Snapshot Fields

FieldMeaning
market, userThe market’s MarketInfo, and the account (checksummed).
block, timestampThe block read and its time.
status, flagsThe market’s status and flags at that block.
stateThe market’s state read, kept for accountHealth.
positionThe position bucket. See below.
hasPositionposition.size > 0.
priceWhat the health and the withdrawal figure were valued at, with its source; null when no price was known at all.
healthThe position’s health at price.spot; null while flat or without a price.
solvencyThe lens’s own view (checkBucketSolvency) at price.spot and the stored funding index; null without a price.
ordersEvery live order: the Limit and StopLimit orders in the account’s active set, then the attached take-profit and stop-loss.
takeProfit, stopLossThe position’s attached TP and SL among orders, or null.
exposureResting orders’ remaining notional (at their limits) and size, per side.
maxWithdrawableThe most withdrawCollateral accepts now, per the lens. See Withdrawals and Fees.
withdrawalBlockedWhy this account cannot withdraw right now, or null.
claimableFeesWhat claimFees would pay this account on this market: refunded bounties, keeper rewards, relayer and integrator fees.
nonceThe account’s next meta-transaction nonce on this pair (gasless requests sign it).
terminalWhat a withdrawal pays while the market settles or after it terminated; null on a live market.
wallet{ usdcBalance, usdcAllowance }, in USDC’s own units (6 decimals): the wallet’s USDC and what this pair may pull.

position (AccountPosition), 18 decimals:

FieldMeaning
isLong, sizeThe net position: one per market, size 0 when flat.
entryValueThe entry notional.
entryPriceentryValue / size, the average entry price (0 while flat).
collateralRaw collateral: deposits plus realized PnL, less fees.
entryFundingIndexThe funding index at entry.
takeProfitOrderId, stopLossOrderIdThe attached TP and SL (0 for none).

terminal (TerminalPayout): collateral, registeredClaim, principalRatio and profitRatio (1e18 = 100%, 0 before finalize), and payout, what a withdrawal pays: collateral × principalRatio + registeredClaim × profitRatio. See Termination & Settlement.

solvency (LensBucketState) is the lens’s figures as the contract computes them: unrealizedPnl, fundingPnl, totalPnl, adjustedSize, currentNotional, entryValue, minRequiredCollateral, availableEquity, effectiveCollateral, adjustedEntryFundingIndex, isSolvent.

Health

snapshot.health (PositionHealth) values the position at a price by the pair’s own formula, at the projected funding index (what a price stored now would accrue to) and the effective maintenance rate:

FieldTypeMeaning
pricebigintThe spot it was valued at.
notionalbigintSize × price.
unrealizedPnlbigintPrice PnL since entry.
fundingPnlbigintFunding earned (positive) or owed (negative) since entry.
totalPnlbigintThe two together.
equitybigintCollateral plus total PnL (signed).
availableEquitybigintEquity floored at zero.
maintenanceRequiredbigintWhat maintenance margin requires at this price.
marginRationumberequity / maintenanceRequired. Below 1, anyone may liquidate the position.
leveragenumber | nullNotional over equity (null when equity is not positive).
isSolventbooleanThe pair’s own solvency verdict.

A margin ratio of 2 means equity is twice what maintenance requires. How maintenance margin and liquidation work is in Margin and Liquidations.

Following the Price

// Revalue at a new price without a read: for a display that follows the live price.
const prices = await bz.livePrices();
prices.subscribe((changed) => {
  const p = changed.get(eth.address);
  const h = p && account.accountHealth(me, p);
  if (h) console.log(`margin ratio ${h.marginRatio.toFixed(2)} at ${formatUnits18(h.price, 2)}`);
}, [eth]);

// Or value at a price you hold, with the market's state from a snapshot read at the same block.
const snap = await bz.markets.marketSnapshot(eth);
const atMine = await bz.account.accountSnapshot(eth, user, { market: snap, price: { spot: 2_900n * 10n ** 18n } });
const whatIf = account.positionHealth(atMine.position, atMine.state, 2_500n * 10n ** 18n);

account.accountHealth(snapshot, price) revalues a snapshot’s position at a new spot (a bigint, or anything with spot, such as a live MarketPrice) without any read: a display that follows the price between snapshots calls it on every tick. It returns null while flat. account.positionHealth(position, state, spot) is the same computation from a position and a market state, for “what if the price were …” figures.

The estimated liquidation price of a new or changed position is in Previews.

Orders

for (const o of me.orders) {
  const life = o.neverExpires ? "never expires" : `${o.blocksLeft} blocks left`;
  console.log(`#${o.orderId} ${o.typeName} ${o.isLong ? "buy" : "sell"} ${formatUnits18(o.remaining)} @ ${formatUnits18(o.limitPrice, 2)}, ${life}`);
}
if (me.stopLoss) console.log(`stop loss at ${formatUnits18(me.stopLoss.triggerPrice, 2)}, bounty ${formatUnits18(me.stopLoss.bounty)}`);
console.log(`resting buys ${formatUnits18(me.exposure.longNotional, 2)} USD, sells ${formatUnits18(me.exposure.shortNotional, 2)} USD`);

Each entry of snapshot.orders is an AccountOrder:

FieldMeaning
orderIdThe pair’s id for the order.
orderType, typeNameThe type (OrderType), and its name: “Market”, “Limit”, “StopLimit”, “TakeProfit”, “StopLoss”.
isLongA buy.
isPostOnly, tifPost-only, and the time in force (0 GTC, 1 IOC, 2 FOK).
size, filledThe order’s size and what has filled.
remainingWhat is left to fill; for a TP or SL, capped at the position it closes.
limitPrice, triggerPriceThe limit, and the trigger (StopLimit, TakeProfit, StopLoss; 0 otherwise).
bountyThe keeper bounty the order still holds in escrow.
maxSlippageBpThe slippage cap of a TP or SL.
expiryBlock, neverExpires, blocksLeftThe expiry (an Arbitrum block), whether it never expires (TP and SL), and blocks until it does (null when never).
inBookResting in the book: a Limit, or a StopLimit that has armed.
closesWholePositionA TP or SL sized to close the whole position, whatever it is when it fires.
attached"takeProfit", "stopLoss", or null.
integratorThe integrator the order names.
recordThe order as stored, every field.

Only live orders are listed: filled, canceled and expired ones are left out, even before a sweep has unlinked them. exposure (OrderExposure) has longNotional, shortNotional, longSize and shortSize: what the account’s resting orders would add. To amend or cancel, see Managing Orders; for the account’s past orders, History.

Withdrawals and Fees

// maxWithdrawable is the lens's figure; withdrawalBlocked is what the market's flags forbid now.
const canWithdraw = me.withdrawalBlocked ? 0n : me.maxWithdrawable;
console.log(me.withdrawalBlocked ?? `up to ${formatUnits18(canWithdraw, 2)} USDC can be withdrawn`);
console.log(`claimable fees ${formatUnits18(me.claimableFees, 2)} USDC`); // claim with bz.trade.claimFees
console.log(`wallet ${formatUnits18(fromUsdcUnits(me.wallet.usdcBalance), 2)} USDC, allowance ${me.wallet.usdcAllowance} units`);
if (me.terminal) console.log(`the market is winding down: a withdrawal pays ${formatUnits18(me.terminal.payout, 2)} USDC`);

maxWithdrawable is the lens’s getMaxWithdrawable, valued at the conservative side of the price for a position (spot less the confidence for a long, plus it for a short): the most the pair would accept now. The lens cannot see a closed venue or a paused market, so check withdrawalBlocked too: when it is set, a withdrawal reverts whatever maxWithdrawable says. withdrawalBlocked is the market’s allWithdrawalsPaused reason, or, for an account holding a position, its positionWithdrawalsFrozen reason. A flat account can withdraw during ADL and while the venue is closed.

claimableFees is what bz.trade.claimFees(market, user) would pay. The withdrawal and claim calls are on Collateral, Approvals & Permits; the exact limits and freezes of a withdrawal are in Previews (previewWithdrawal).

Portfolios

const book = await bz.account.portfolio(user); // every market where the account holds anything
for (const a of book.accounts) {
  console.log(`${a.market.label}: collateral ${formatUnits18(a.position.collateral, 2)}, ${a.orders.length} orders`);
}
const t = book.totals;
console.log(`equity ${formatUnits18(t.equity, 2)}, withdrawable ${formatUnits18(t.maxWithdrawable, 2)}, fees ${formatUnits18(t.claimableFees, 2)}`);
const two = await bz.account.portfolio(user, ["ETH/USD", "BTC/USD"], { prices }); // chosen markets, live prices

bz.account.portfolio(user, markets?, options?) returns a snapshot for every market where the account has collateral, a position, live orders or claimable fees, in the factory’s order, with totals. Without markets it covers the factory’s whole list, and takes the claimable fees from one getClaimableFeesAll read, which also catches a market listed since the list was cached. It costs about four calls whatever the number of markets: the head, one batch over every market, then one batch for the markets the account uses, sent with their state read. Its options are block, prices and fetchPrices, as for accountSnapshot.

FieldMeaning
user, block, timestampThe account and the block read.
accountsOne AccountSnapshot per market the account uses.
walletUsdcThe wallet’s USDC, in USDC units (6 decimals).
totalsSummed across accounts, 18 decimals: collateral, equity (collateral plus PnL where there is a position), unrealizedPnl, fundingPnl, claimableFees, and maxWithdrawable (0 for a market whose withdrawals are blocked).

Keeping It Current

bz.live.watchAccount(user, markets, onUpdate) delivers a fresh snapshot for each market an event touches (a fill, an order, a deposit, a liquidation) and for every market every 15 seconds. See Live Updates.

What Can Go Wrong

WhatWhy
Error: could not read the state of market ...The market’s own state read failed at that block.
price is null, health is null with a positionNo price at all: no live prices, no Pyth Pro source (or it failed), and the pair has never stored one.
price.source is "stored"No fresher price could be had; the stored price may be minutes old. Show price.publishTime.
maxWithdrawable is positive but a withdrawal revertswithdrawalBlocked is set (a closed venue, ADL, a pause), or the price moved. Simulate first.
An address erroruser is not an address (viem’s InvalidAddressError).