> ## Documentation Index
> Fetch the complete documentation index at: https://public-perps-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Utilities

> Calculation helpers, order classification, formatting, and validation utilities

The SDK exports utility functions for position math, TP/SL calculations, fee estimation, order classification, and formatting. These are stateless pure functions — they do not call the API.

## Position Calculations

```typescript theme={null}
import {
  calculatePositionSize,
  calculateNotionalValue,
  calculateUnrealizedPnl,
  calculateRoe,
  calculateRequiredMargin,
  calculateRealizedPnlPercent,
  effectiveLeverage,
  liquidationDistancePercent,
  removableIsolatedMargin,
} from '@lifi/perps-sdk';
```

### calculatePositionSize

Calculate position size from margin, leverage, and price.

```typescript theme={null}
const size = calculatePositionSize(marginUsd, leverage, price);
// e.g. calculatePositionSize(1000, 10, 95000) → position size for $10k notional
```

| Parameter   | Type     | Description          |
| ----------- | -------- | -------------------- |
| `marginUsd` | `number` | Margin amount in USD |
| `leverage`  | `number` | Leverage multiplier  |
| `price`     | `number` | Entry price          |

### calculateNotionalValue

```typescript theme={null}
const notional = calculateNotionalValue(size, price);
```

### calculateUnrealizedPnl

```typescript theme={null}
const pnl = calculateUnrealizedPnl(entryPrice, currentPrice, size);
```

### calculateRoe

Return on equity (PnL / margin).

```typescript theme={null}
const roe = calculateRoe(pnl, margin);
```

### calculateRequiredMargin

```typescript theme={null}
const margin = calculateRequiredMargin(notionalValue, leverage);
```

### calculateRealizedPnlPercent

```typescript theme={null}
const pnlPercent = calculateRealizedPnlPercent(realizedPnl, size, price);
```

### effectiveLeverage

Effective leverage of an open position (`positionValueUsd / marginUsd`); `0` when margin is zero. Takes a single params object.

```typescript theme={null}
effectiveLeverage({ positionValueUsd: 10000, marginUsd: 1000 }); // 10
```

| Parameter          | Type     | Description                        |
| ------------------ | -------- | ---------------------------------- |
| `positionValueUsd` | `number` | Position notional value in USD     |
| `marginUsd`        | `number` | Margin backing the position in USD |

### liquidationDistancePercent

Absolute distance from the current price to the liquidation price, as a percentage of the current price; `0` when the current price is zero.

```typescript theme={null}
liquidationDistancePercent({ liquidationPrice: 90000, currentPrice: 95000 }); // ~5.26
```

| Parameter          | Type     | Description                      |
| ------------------ | -------- | -------------------------------- |
| `liquidationPrice` | `number` | The position's liquidation price |
| `currentPrice`     | `number` | Current market price             |

### removableIsolatedMargin

Calculate the exact margin that can be removed from an isolated position using the provider's retained-margin requirement. The result includes unrealized PnL and snaps down to the venue's accepted amount increment. Cross-margined positions and markets whose `positionMarginAdjustment` is `NONE` or `ADD_ONLY` return `'0'`.

```typescript theme={null}
const constraints = perps.getPositionMarginConstraints(position);
const removable = constraints
  ? removableIsolatedMargin({ position, constraints })
  : '0';
```

| Parameter     | Type                        | Description                                                                                     |
| ------------- | --------------------------- | ----------------------------------------------------------------------------------------------- |
| `position`    | `Position`                  | Current isolated position, including margin, unrealized PnL, margin mode, and market capability |
| `constraints` | `PositionMarginConstraints` | Provider-owned `minimumMarginRequirement` and `amountIncrement` decimal strings                 |

Throws `PerpsError` with `ValidationError` for malformed, non-positive, or inconsistent risk inputs.

***

## Position Math (Predictive)

Pure-function helpers for predicting how a perp position changes after a fill — used by add-to-position and partial-close preview blocks. These read SDK shapes as plain numbers (callers parse `Position.size`, `entryPrice`, etc. from string), and use the convention long = +1, short = -1. Sizes passed in are non-negative magnitudes; direction is carried by `isLong`.

```typescript theme={null}
import {
  directionSign,
  estimateIsolatedLiquidationPrice,
  predictAverageEntryPrice,
  predictNewLeverage,
  predictUnrealizedPnl,
  realizedPnlOnClose,
} from '@lifi/perps-sdk';
```

### directionSign

Direction sign for a position.

```typescript theme={null}
directionSign(true);  // 1   (long)
directionSign(false); // -1  (short)
```

| Parameter | Type      | Description                              |
| --------- | --------- | ---------------------------------------- |
| `isLong`  | `boolean` | True for long positions, false for short |

**Returns:** `1 | -1`

### estimateIsolatedLiquidationPrice

Estimated liquidation price for a **new** isolated-margin position, parameterised by the venue's maintenance margin rate. Returns `undefined` when the inputs cannot produce one (zero leverage, degenerate denominator). For existing positions, prefer `Position.liquidationPrice` from the venue.

```typescript theme={null}
estimateIsolatedLiquidationPrice({
  entryPrice: 95000,
  leverage: 10,
  isLong: true,
  maintenanceMarginRate: 0.01,
});
```

| Parameter               | Type      | Description                                                    |
| ----------------------- | --------- | -------------------------------------------------------------- |
| `entryPrice`            | `number`  | Position entry price                                           |
| `leverage`              | `number`  | Position leverage                                              |
| `isLong`                | `boolean` | True for long, false for short                                 |
| `maintenanceMarginRate` | `number`  | Venue maintenance margin rate as a fraction (e.g. `0.01` = 1%) |

**Returns:** `number | undefined`

### predictAverageEntryPrice

Predicted average entry price after adding to an existing position. Weighted average of current entry and the new fill price, weighted by leg size in coin units. Both legs must be in the same direction (this helper is for adding to, not flipping, a position).

```typescript theme={null}
const newEntry = predictAverageEntryPrice({
  currentSize: 1,
  currentEntry: 95_000,
  addSize: 0.5,
  fillPrice: 96_500,
});
// 95_500
```

| Parameter      | Type     | Description                                                                              |
| -------------- | -------- | ---------------------------------------------------------------------------------------- |
| `currentSize`  | `number` | Existing position size in coin units (>= 0)                                              |
| `currentEntry` | `number` | Existing position's average entry price                                                  |
| `addSize`      | `number` | Size being added in coin units (>= 0)                                                    |
| `fillPrice`    | `number` | Price the new size is expected to fill at (mid for market, limit price for limit orders) |

**Returns:** `number | undefined` — the new weighted-average entry price, or `undefined` if the inputs cannot produce a valid average (zero combined size, non-finite values).

### predictNewLeverage

Predicted effective leverage (`totalNotional / totalMargin`) after adding margin and notional. The caller computes notional from size and price (e.g. with `calculateNotionalValue`) and supplies the additional margin the user is about to put up.

```typescript theme={null}
const newLeverage = predictNewLeverage({
  currentNotional: 10_000,
  currentMargin: 1_000,
  addNotional: 5_000,
  addMargin: 500,
});
// 10
```

| Parameter         | Type     | Description               |
| ----------------- | -------- | ------------------------- |
| `currentNotional` | `number` | Current position notional |
| `currentMargin`   | `number` | Current position margin   |
| `addNotional`     | `number` | Notional being added      |
| `addMargin`       | `number` | Margin being added        |

**Returns:** `number | undefined` — the new effective leverage, or `undefined` if total margin is non-positive.

### predictUnrealizedPnl

Predicted unrealised PnL at the current mark price: `(markPrice - entryPrice) * size * directionSign(isLong)`.

```typescript theme={null}
const uPnl = predictUnrealizedPnl({
  entryPrice: 95_000,
  markPrice: 96_000,
  size: 1,
  isLong: true,
});
// 1_000
```

| Parameter    | Type      | Description                               |
| ------------ | --------- | ----------------------------------------- |
| `entryPrice` | `number`  | Position entry price                      |
| `markPrice`  | `number`  | Current mark price                        |
| `size`       | `number`  | Position size as a non-negative magnitude |
| `isLong`     | `boolean` | Direction of the position                 |

**Returns:** `number`

### realizedPnlOnClose

Realised PnL on the portion of a position being closed: `(closePrice - entryPrice) * closeSize * directionSign(isLong)`.

```typescript theme={null}
const rPnl = realizedPnlOnClose({
  entryPrice: 95_000,
  closePrice: 96_000,
  closeSize: 0.5,
  isLong: true,
});
// 500
```

| Parameter    | Type      | Description                                   |
| ------------ | --------- | --------------------------------------------- |
| `entryPrice` | `number`  | Position entry price                          |
| `closePrice` | `number`  | Price the close fills at                      |
| `closeSize`  | `number`  | Size being closed as a non-negative magnitude |
| `isLong`     | `boolean` | Direction of the position being closed        |

**Returns:** `number`

***

## Order Math (Expected rPnL Previews)

Helpers for previewing the realised PnL the user would lock in if a resting order filled against a matching position. Used by Orders-tab rows on the trading UI. These compose `directionSign` and `realizedPnlOnClose` from [Position Math](#position-math-predictive), so the two modules stay in lockstep.

Orders that open or add to a position have no defined rPnL and return `null` (typically rendered as an em-dash in the UI).

```typescript theme={null}
import {
  expectedRealizedPnlForOpenOrder,
  expectedRealizedPnlForTriggerOrder,
  findMatchingPosition,
  resolveCloseSize,
} from '@lifi/perps-sdk';
import type { OpenOrder, Position, TriggerOrder } from '@lifi/perps-sdk';
```

### findMatchingPosition

Pick the matching open position for an order's asset, if any.

```typescript theme={null}
const matching = findMatchingPosition(order.market.id, positions);
```

| Parameter   | Type                  | Description                      |
| ----------- | --------------------- | -------------------------------- |
| `marketId`  | `string`              | The order's `market.id`          |
| `positions` | `readonly Position[]` | Open positions list (any symbol) |

**Returns:** `Position | undefined`

### resolveCloseSize

Resolve the close-size against a position, applying the spec's cap rules:

* `orderSize === 0` is the Hyperliquid convention for "close entire position" used by trigger orders → close the full position size.
* Otherwise cap at the absolute position size; an order larger than the position can only close what's open.

Inputs are non-negative magnitudes.

```typescript theme={null}
resolveCloseSize(0, 1.5);   // 1.5  (zero == close-all)
resolveCloseSize(0.5, 1.5); // 0.5  (partial close)
resolveCloseSize(2, 1.5);   // 1.5  (capped at position)
```

| Parameter      | Type     | Description                                |
| -------------- | -------- | ------------------------------------------ |
| `orderSize`    | `number` | Order size, non-negative magnitude         |
| `positionSize` | `number` | Open position size, non-negative magnitude |

**Returns:** `number`

### expectedRealizedPnlForOpenOrder

Expected rPnL for a resting limit order against a matching position.

Reducing requires opposite sides (long position + `SELL`, short position + `BUY`). Same-side orders add to the position and return `null`. Returns `null` if there is no matching position or the inputs are non-finite.

```typescript theme={null}
import { expectedRealizedPnlForOpenOrder, findMatchingPosition } from '@lifi/perps-sdk';
import type { OpenOrder, Position } from '@lifi/perps-sdk';

function previewOrderRPnl(order: OpenOrder, positions: readonly Position[]) {
  const position = findMatchingPosition(order.market.id, positions);
  return expectedRealizedPnlForOpenOrder(order, position);
  // number when the order would reduce the position; null otherwise
}
```

| Parameter  | Type                    | Description                    |
| ---------- | ----------------------- | ------------------------------ |
| `order`    | `OpenOrder`             | The resting limit order        |
| `position` | `Position \| undefined` | Matching open position, if any |

**Returns:** `number | null`

### expectedRealizedPnlForTriggerOrder

Expected rPnL for a TP/SL trigger order against a matching position.

A trigger order is by construction a closing leg — its direction is the opposite of the position's. With no matching position there is nothing to close, so rPnL is `null`. The trigger price (always present on the SDK `TriggerOrder` shape) is used as the rPnL price; the optional `limitPrice` for `STOP_LIMIT` / `TAKE_PROFIT_LIMIT` is the post-trigger limit, not the rPnL price.

```typescript theme={null}
import {
  expectedRealizedPnlForTriggerOrder,
  findMatchingPosition,
} from '@lifi/perps-sdk';
import type { Position, TriggerOrder } from '@lifi/perps-sdk';

function previewTriggerRPnl(order: TriggerOrder, positions: readonly Position[]) {
  const position = findMatchingPosition(order.market.id, positions);
  return expectedRealizedPnlForTriggerOrder(order, position);
}
```

| Parameter  | Type                    | Description                    |
| ---------- | ----------------------- | ------------------------------ |
| `order`    | `TriggerOrder`          | The TP/SL trigger order        |
| `position` | `Position \| undefined` | Matching open position, if any |

**Returns:** `number | null`

***

## TP/SL Calculations

```typescript theme={null}
import {
  calculateExpectedPnl,
  priceFromPercent,
  percentFromPrice,
} from '@lifi/perps-sdk';
```

### calculateExpectedPnl

Calculate expected PnL if a trigger price is hit.

```typescript theme={null}
const { amount, percent } = calculateExpectedPnl(
  triggerPrice,
  entryPrice,
  leverage,
  isLong,
  margin
);
```

| Parameter      | Type      | Description              |
| -------------- | --------- | ------------------------ |
| `triggerPrice` | `number`  | TP/SL trigger price      |
| `entryPrice`   | `number`  | Position entry price     |
| `leverage`     | `number`  | Leverage                 |
| `isLong`       | `boolean` | Whether position is long |
| `margin`       | `number`  | Position margin          |

**Returns:** `ExpectedPnl | null` — `{ amount: number; percent: number }` (signed; positive = profit). Returns `null` when `triggerPrice`, `entryPrice`, or `margin` is zero.

### priceFromPercent

Convert a target PnL percentage to a trigger price.

```typescript theme={null}
const price = priceFromPercent(percent, entryPrice, leverage, isLong);
// e.g. priceFromPercent(50, 95000, 10, true) → price for +50% ROE on a 10x long
```

### percentFromPrice

Convert a trigger price to a PnL percentage.

```typescript theme={null}
const percent = percentFromPrice(price, entryPrice, leverage, isLong);
```

***

## Fee & Slippage

```typescript theme={null}
import { estimateFees, applySlippage } from '@lifi/perps-sdk';
```

### estimateFees

```typescript theme={null}
const fees = estimateFees(sizeUsd, feeRate);
// e.g. estimateFees(10000, 0.0005) → 5.0
```

### applySlippage

Adjust a price by a slippage percentage.

```typescript theme={null}
const adjusted = applySlippage(price, slippagePercent, isBuy);
// Buy: price goes up. Sell: price goes down.
```

***

## Quote Building

Pure helpers behind [`getQuote`](/sdk/streaming#getquote) / [`subscribeQuote`](/sdk/streaming#subscribequote) — walk a book to a VWAP fill and assemble a `Quote`. Most consumers call the client methods; reach for these to compute a quote from a book snapshot you already hold.

```typescript theme={null}
import { walkOrderbook, buildQuote } from '@lifi/perps-sdk';
```

### walkOrderbook

Walk one side of an orderbook to fill `sizeUsd` notional, accumulating base size and notional level-by-level to derive the VWAP fill. Levels are consumed in array order — pass asks for a buy and bids for a sell, each ordered best-price-first. Throws `PerpsError` (`ValidationError`) on a malformed level.

```typescript theme={null}
const walk = walkOrderbook(asks, 10_000);
// { baseSize, filledNotional, vwap, insufficientLiquidity }
```

| Parameter | Type               | Description                     |
| --------- | ------------------ | ------------------------------- |
| `levels`  | `OrderbookLevel[]` | One book side, best-price-first |
| `sizeUsd` | `number`           | USD notional to fill            |

**Returns:** `BookWalk` — `{ baseSize, filledNotional, vwap, insufficientLiquidity }`. `insufficientLiquidity` is `true` when the book cannot absorb the full notional (the walk returns the best obtainable fill).

### buildQuote

Assemble a `Quote` from a resolved market, its live `MarketContext`, and its orderbook snapshot: walks the relevant side for the VWAP fill, derives price impact in basis points versus mark, and applies the base taker fee on the filled notional. Throws `PerpsError` (`ValidationError`) on a malformed book level.

```typescript theme={null}
const quote = buildQuote(input); // input: { provider, symbol, type, side, sizeUsd, market, price, bids, asks, feeTier, timestamp }
```

**Returns:** `Quote`.

***

## Account Summary

There is no standalone `calculateAccountSummary` util. Derive portfolio metrics with the [`PerpsClient.getAccountSummary()`](/sdk/trading/methods) method:

```typescript theme={null}
const summary = perps.getAccountSummary(account, positions);
// { portfolioValue, availableMargin, marginUsed, unrealizedPnl } — all strings
```

| Parameter   | Type              | Description                     |
| ----------- | ----------------- | ------------------------------- |
| `account`   | `AccountResponse` | Account from `getAccount()`     |
| `positions` | `Position[]`      | Positions from `getPositions()` |

**Returns:** `AccountSummary` — `{ portfolioValue, availableMargin, marginUsed, unrealizedPnl }`, **all `string` fields**.

The underlying pure function is `summarizeAccount(account, positions, semantics)`, exported from `@lifi/perps-sdk`. The third argument, `semantics: CollateralSemantics` (`'free' | 'net' | 'gross' | 'equity'`), tells the roll-up how the collateral rows relate to the positions' locked margin and unrealized PnL:

| Value      | Meaning                                                                                                                                                   |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `'free'`   | Collateral rows exclude locked margin and unrealized PnL; both are added back to compute buying power.                                                    |
| `'net'`    | Collateral rows exclude locked margin but already include unrealized PnL (e.g. a venue-reported available balance); only the locked margin is added back. |
| `'gross'`  | Collateral rows already include locked margin; unrealized PnL is added on top of available margin.                                                        |
| `'equity'` | Collateral rows are total equity (locked margin and unrealized PnL already included).                                                                     |

***

## Order Classification

```typescript theme={null}
import {
  isTakeProfitOrder,
  isStopLossOrder,
  isTpSlOrder,
  classifyFill,
  classifyFillFromPosition,
  ACTIVE_ORDER_STATUSES,
  isActiveOrderStatus,
} from '@lifi/perps-sdk';
```

### isTakeProfitOrder / isStopLossOrder / isTpSlOrder

Check whether an order is a TP, SL, or either.

```typescript theme={null}
if (isTpSlOrder(order)) {
  console.log('This is a trigger order');
}
```

### classifyFill

<Warning>
  `classifyFill` is **deprecated** — read `Fill.classification` (already populated on every fill) instead.
</Warning>

Classify a fill based on side and realized PnL. Returns a `FillClassification` enum value (Title-Case strings):

```typescript theme={null}
const type = classifyFill(side, realizedPnl);
// FillClassification — e.g. 'Opened Long' | 'Opened Short' | 'Closed Long' | 'Closed Short'
```

### classifyFillFromPosition

Classify a fill into the full Open/Close/Increase/Reduce/Switch taxonomy using the signed position held **before** the fill. This is the accurate classifier `classifyFill` is deprecated in favour of.

```typescript theme={null}
classifyFillFromPosition('0', 'B', '0.5');   // 'Opened Long'
classifyFillFromPosition('1.0', 'A', '1.0'); // 'Closed Long'
```

| Parameter       | Type     | Description                                                          |
| --------------- | -------- | -------------------------------------------------------------------- |
| `startPosition` | `string` | Signed position before this fill (`> 0` long, `< 0` short, `0` flat) |
| `side`          | `string` | `'B'` for buy; anything else is treated as a sell                    |
| `sz`            | `string` | Unsigned fill size                                                   |

**Returns:** `FillClassification`.

### ACTIVE\_ORDER\_STATUSES / isActiveOrderStatus

`ACTIVE_ORDER_STATUSES` is the `ReadonlySet<OrderStatus>` of statuses for an order still resting on the book (`OPEN`, `PENDING`, `PARTIALLY_FILLED`, `TRIGGERED`); anything else is terminal and should be evicted from a cached orders list on a WS update. `isActiveOrderStatus(status)` is the membership check.

```typescript theme={null}
if (!isActiveOrderStatus(update.status)) {
  removeFromCache(update.id);
}
```

***

## Validation

```typescript theme={null}
import { validateMargin } from '@lifi/perps-sdk';
```

### validateMargin

Check whether the user has sufficient margin for an order.

```typescript theme={null}
const result = validateMargin(margin, leverage, availableBalance, feeRate, minMarginUsd);
// '' (valid) | 'insufficient' | 'below-minimum'
```

| Parameter          | Type     | Description              |
| ------------------ | -------- | ------------------------ |
| `margin`           | `number` | Margin to allocate       |
| `leverage`         | `number` | Leverage multiplier      |
| `availableBalance` | `number` | User's available balance |
| `feeRate`          | `number` | Expected fee rate        |
| `minMarginUsd`     | `number` | Minimum margin in USD    |

***

## Parsing & Conversion

```typescript theme={null}
import { stringToFloat, fromBaseUnits, fromBaseUnitsNumber } from '@lifi/perps-sdk';
```

### stringToFloat

Parse formatted currency/percentage strings to a number.

```typescript theme={null}
stringToFloat('$1,234.56'); // 1234.56
stringToFloat('50%');        // 50
```

### fromBaseUnits / fromBaseUnitsNumber

Convert a base-unit amount (passed as a **string**) to a decimal string or number.

```typescript theme={null}
fromBaseUnits('1000000', 6);       // '1'
fromBaseUnitsNumber('1000000', 6); // 1
```

***

## Formatting

Display formatters for USD amounts, prices, and percentages. Each accepts a `FormatInput` (number, numeric string, or null/undefined) and an optional `FormatOptions`; non-finite input renders the placeholder (default `'—'`).

```typescript theme={null}
import {
  formatNumber,
  formatUsd,
  formatSignedUsd,
  formatSignedPercent,
  formatPrice,
  formatCompactUsd,
} from '@lifi/perps-sdk';
```

`FormatOptions` is shared by every formatter:

| Field         | Type           | Default     | Description                                     |
| ------------- | -------------- | ----------- | ----------------------------------------------- |
| `decimals`    | `number`       | `2`         | Fraction digits                                 |
| `placeholder` | `string`       | `'—'`       | Rendered for non-finite input                   |
| `locale`      | `string`       | host locale | BCP-47 locale for digit grouping                |
| `rounding`    | `RoundingMode` | `'halfUp'`  | `'halfUp'` or `'floor'` (truncates toward zero) |

### formatNumber

Currency-symbol-free core formatter: two decimals with locale digit grouping by default. The other formatters build on it.

```typescript theme={null}
formatNumber(1234.5);                      // '1,234.50'
formatNumber(1234.567, { rounding: 'floor' }); // '1,234.56'
```

### formatUsd

Unsigned USD, two decimals with digit grouping. Negatives place the `-` before the `$`.

```typescript theme={null}
formatUsd(1234.5);  // '$1,234.50'
formatUsd(-1500);   // '-$1,500.00'
```

### formatSignedUsd

USD with an explicit sign, derived after rounding (so sub-cent magnitudes render `$0.00`).

```typescript theme={null}
formatSignedUsd(1.43);  // '+$1.43'
formatSignedUsd(-1.43); // '-$1.43'
```

### formatSignedPercent

Percentage with an explicit sign and no grouping.

```typescript theme={null}
formatSignedPercent(1.43);  // '+1.43%'
formatSignedPercent(-1.43); // '-1.43%'
```

### formatPrice

Unsigned price with decimals auto-detected from magnitude (override with `options.decimals`). Grouping applies once the absolute value reaches 1000.

```typescript theme={null}
formatPrice(1234.5);  // '$1,234.50'
formatPrice(0.1234);  // '$0.1234'
```

### formatCompactUsd

USD with a `B`/`M`/`K` suffix.

```typescript theme={null}
formatCompactUsd(1_230_000_000); // '$1.23B'
formatCompactUsd(45_600_000);    // '$45.60M'
```

***

## Explorer

```typescript theme={null}
import { explorerTxUrl, explorerTxUrlFromBase, ExplorerChainId } from '@lifi/perps-sdk';
```

### explorerTxUrl / ExplorerChainId

Resolve a tx hash to a fully-qualified block-explorer URL for the settling chain. `ExplorerChainId` is the const map of supported settling chains (`ETHEREUM` `1`, `ARBITRUM_ONE` `42161`, `LIGHTER` `304`, `HYPERLIQUID` `999`) and the union of its values. `explorerTxUrl` returns `undefined` for an empty hash (no on-chain tx to link).

```typescript theme={null}
explorerTxUrl(ExplorerChainId.HYPERLIQUID, txHash);
// 'https://app.hyperliquid.xyz/explorer/tx/0x...'
```

| Parameter | Type                  | Description                           |
| --------- | --------------------- | ------------------------------------- |
| `chainId` | `ExplorerChainId`     | Settling chain the tx was observed on |
| `txHash`  | `string \| undefined` | Transaction hash                      |

**Returns:** `string | undefined`. Use `explorerTxUrlFromBase(baseUrl, txHash)` for provider instances whose explorer is configured by URL rather than one of the known `ExplorerChainId` values; it returns `undefined` when either input is absent.

***

## Deposit Assets and Gas

Deposit destinations are provider-owned and resolved through `PerpsClient.getDepositFlow()`. The SDK exports canonical on-chain asset identities for consumers that render or execute those flows:

```typescript theme={null}
import {
  ETHEREUM_NATIVE_GAS,
  ETHEREUM_USDC,
  HYPERLIQUID_USDC,
  LIGHTER_USDC,
  ROBINHOOD_NATIVE_GAS,
  ROBINHOOD_USDG,
  getGasRecommendation,
} from '@lifi/perps-sdk';
```

Each asset is a `DeclaredDepositAsset` with `{ chainId, address, decimals }`; identity is the chain/address pair, not the display symbol. `getGasRecommendation(client, { chainId })` calls LI.FI's gas suggestion API for the native-gas leg of a `firstDepositPipeline`. It returns `GasRecommendationResponse`, including `available: false` when LI.FI cannot source gas for that chain.

***

## Exact Integer Scaling

Use `scaleToInteger(value, decimals, policy)` when a provider wire format requires an integer amount. It performs exact decimal arithmetic and requires an explicit off-grid policy: `'truncate'` moves toward zero for sizes and collateral, while `'round'` snaps prices half away from zero.

```typescript theme={null}
import { scaleToInteger } from '@lifi/perps-sdk';

scaleToInteger('0.29', 2, 'truncate'); // 29
```

Invalid decimal strings, negative/non-integer precision, and values beyond `Number.MAX_SAFE_INTEGER` throw `PerpsError` with `ValidationError`.

***

## Setup and Market Selection

```typescript theme={null}
import {
  isActiveMarket,
  selectUserSetupActions,
  toPerpsMarketDisplay,
} from '@lifi/perps-sdk';
```

* `selectUserSetupActions(provider.setup)` returns only descriptors whose `signers` include `USER`. SDK-only setup steps are completed inline by `checkSetup()` and should not be rendered as user actions.
* `isActiveMarket(market)` is false only when `market.isDelisted === true`.
* `toPerpsMarketDisplay(market)` projects a perpetual market to the identity/capability shape embedded in positions, including `isDelisted` and `positionMarginAdjustment`.

***

## Signing

```typescript theme={null}
import { signTypedData, signTypedDataWithSigner } from '@lifi/perps-sdk';
```

### signTypedData

Sign EIP-712 typed data with a private key. Useful for server-side signing without a wallet. Async — returns `Promise<Hex>`.

```typescript theme={null}
const signature = await signTypedData(privateKey, typedData);
```

| Parameter    | Type             | Description                               |
| ------------ | ---------------- | ----------------------------------------- |
| `privateKey` | `Hex`            | Private key (0x-prefixed)                 |
| `typedData`  | `PerpsTypedData` | EIP-712 typed data from a create endpoint |

### signTypedDataWithSigner

Sign EIP-712 typed data with an externally-provided viem `WalletClient` — a browser wallet (wagmi), private key, or mnemonic. Use this to sign the user arm of a setup step with the end-user's wallet. Async — returns `Promise<Hex>`.

```typescript theme={null}
const signature = await signTypedDataWithSigner(userWallet, typedData);
```

| Parameter    | Type             | Description                               |
| ------------ | ---------------- | ----------------------------------------- |
| `userWallet` | `WalletClient`   | viem wallet client with an account        |
| `typedData`  | `PerpsTypedData` | EIP-712 typed data from a create endpoint |

***

## Hyperliquid-specific

These utilities are specific to Hyperliquid's order formatting requirements and are exported from **`@lifi/perps-sdk-provider-hyperliquid`** (not the core SDK):

```typescript theme={null}
import {
  calculateLiquidationPrice,
  calculateMaintenanceMarginRate,
  formatOrderPrice,
  formatOrderSize,
  getMaxPriceDecimals,
} from '@lifi/perps-sdk-provider-hyperliquid';
```

<Note>
  `calculateLiquidationPrice` and `calculateMaintenanceMarginRate` return `number | undefined` (undefined when inputs can't yield a valid result).
</Note>

### calculateLiquidationPrice

```typescript theme={null}
const liqPrice = calculateLiquidationPrice(entryPrice, leverage, isLong, maxLeverage);
```

### calculateMaintenanceMarginRate

```typescript theme={null}
const mmr = calculateMaintenanceMarginRate(maxLeverage);
```

### formatOrderPrice

Format a price for Hyperliquid submission (5 significant figures, correct decimal places).

```typescript theme={null}
formatOrderPrice(95123.456, 2); // '95123'
```

### formatOrderSize

Format a size for Hyperliquid submission (no trailing zeros).

```typescript theme={null}
formatOrderSize(0.100, 3); // '0.1'
```

### getMaxPriceDecimals

Get the maximum number of price decimal places for an asset.

```typescript theme={null}
const decimals = getMaxPriceDecimals(szDecimals);
```
