Precision & Units
Most integration bugs are unit bugs. This page is the single reference — every other page links back here.
Quick table
| Field | Unit | Example |
|---|---|---|
qty | base asset amount, 1e10 | 0.001 BTC → 10000000 |
price, stopLoss, takeProfit, triggerPrice, entryPrice | USD, 1e18 | $100,000 → 100000000000000000000000 |
amountIn | tokenIn decimals | 10 USDC → 10000000 |
extraFee | tokenIn decimals | see Brokers & Referrals |
margin, openFee, closeFee, holdingFee, fundingFee, pnl | lvToken decimals | LVUSD and LVMON are both 18 |
antiDdosFee | fee token decimals | see Execution Fee |
nonce | milliseconds since epoch, uint64 | 1785312000123 |
deadline | seconds since epoch, uint48 | 1785312300 |
Percentage fields ending in P | 1e4 unless noted | 50 → 0.5% |
holdingFeeRate | 1e12 | |
fundingFeeRate, longAccFundingFeePerShare | 1e18, signed | |
shareP, minCloseFeeP, lvTokenDiscountP | 1e5 |
qty is the trap
qty is not notional USD and not wei. It is the amount of the base asset, scaled by 1e10 — the same scale for every pair, regardless of the asset's own decimals.
To convert from a USD position size:
import { parseUnits } from 'viem'
// $100 of BTC at $100,000
const notionalUsd = 100
const price = 100_000
const qty = parseUnits((notionalUsd / price).toFixed(10), 10)
// 0.001 BTC -> 10_000_000nGoing the other way, when reading a position:
import { formatUnits } from 'viem'
const baseAmount = formatUnits(position.qty, 10) // "0.001"
const notionalUsd =
(Number(position.qty) / 1e10) * (Number(position.entryPrice) / 1e18)WARNING
.toFixed(10) before parseUnits is not optional. 100 / 100000 in JavaScript is 0.001, but many other pairs produce values with more than 10 decimal places, and parseUnits throws on excess precision.
Prices are always 1e18
Every price field in the protocol — the acceptable price on an open, stop-loss, take-profit, trigger prices on TP/SL orders, and the entry/close prices you read back — is USD at 18 decimals. This holds regardless of the collateral token's decimals.
const price = parseUnits('100000', 18) // $100,000Amounts follow the token
amountIn is denominated in tokenIn's own decimals. USDC is 6; WMON, LVUSD and LVMON are 18. Read decimals() rather than assuming.
Values inside a position (margin, fees, PnL) are denominated in the lvToken, not tokenIn. A position funded with 10 USDC (amountIn at 6 decimals) reports its margin in LVUSD at 18 decimals — the two are not the same scale. See Collateral and lvToken.
Percentages
Fields whose name ends in P are integer percentages with an implied scale. The common case is 1e4, where 10000 = 100%:
const slippagePercent = Number(config.slippageLongP) / 100 // 50 -> 0.5%The exceptions are listed in the table above — shareP, minCloseFeeP and lvTokenDiscountP use 1e5, and holdingFeeRate uses 1e12.
Signed values
fundingFee, accruedFundingFee, pnl and fundingFeeRate are signed. A positive funding fee means the trader receives; negative means the trader pays. Decode them as int256/int96, not uint.
JSON transport
REST API responses and 1CT request bodies carry large integers as strings, not JSON numbers. nonce in particular exceeds Number.MAX_SAFE_INTEGER and will silently lose precision if you let JSON.stringify write it as a number.
body: JSON.stringify({
nonce: nonce.toString(), // correct
antiDdosFee: antiDdosFee.toString(),
})