Addendum to the main spec. These are new/populated fields on existing endpoints.
Base URL (dev): https://api.opbalance.com/mm/spot
Every position row now includes:
{
// ... existing fields ...
healthFactor: string // account-wide, equity/maint. "999999" when no debt.
partialLiquidationPrice: string // price at which HF < target → soft seizure begins
fullLiquidationPrice: string // price at which HF = 1.0 → full closeout (Regime B)
liquidationPrice: string // legacy alias = partialLiquidationPrice
}ftDNMM has three liquidation regimes (from /home/pe/newnewrepos/w/flyingtulip/ftDNMM):
| Regime | HF range | What happens |
|---|---|---|
| A | HF ≥ target (e.g. 1.5) |
Safe. No liquidation possible. |
| B | 1.0 ≤ HF < target |
Soft liquidation. Liquidator seizes just enough to restore HF to target. |
| C | HF < 1.0 |
Forced full closeout. All collateral seized. |
partialLiquidationPrice= price at which you enter Regime B (warning — start of soft seizure)fullLiquidationPrice= price at which you enter Regime C (full closeout)- For a long position:
partialLiquidationPrice > fullLiquidationPrice(partial triggers earlier as price falls) - For a short position: inverted — partial triggers earlier as price rises
Show both:
Liq. price: $38,450 (partial) / $32,100 (full)
HF: 1.52
Or a single price with colored warning:
⚠ Soft liq at $38,450 | 💥 Full liq at $32,100
Legacy liquidationPrice keeps the partial value so nothing breaks if you're already using it.
- All three return
"0"if the on-chain call reverts (log-warned server-side, never 500). healthFactorreturns"999999"whenmaintUSDWad == 0(user has collateral but no debt). Treat as infinity.
Previously the following were placeholder "0". Now live for spot rows:
{
symbol: "WS-USDC",
marketType: "spot",
marketCap: "10874864.5551", // totalSupply × oracle price / 10^decimals
openInterest: "30.0317", // LendingLens.astate.borrows × price
fundingRate: "57.6597%", // IRM.borrowAPR(asset, util)
priceChange24h: "+0.0020", // from 24h candle window
priceChangePct24h: "+4.5677%", // same
volume24h: "0", // still zero (no trade volume tracking yet — TODO)
}Perps twin rows still emit marketCap: "0" because perps have no supply. Other fields populate for both.
{
token: "WS",
totalBalance: "1234.5678",
usdValue: "55.28", // balance × oracle.priceUSD(token) / 10^decimals / 1e18
pnlPct: "0%", // still placeholder
}Falls back to ticker markPrice if oracle reverts, then to "0" if both fail.
Server-side TTL cache (transparent to client):
- Oracle prices: 5s
- All other on-chain reads (assetCfg, accountValues, etc.): 30s
- Failures cached briefly (~5s) to avoid hammering RPC on reverts
- Per-key mutex coalesces concurrent readers — no thundering herd
| Field | Why |
|---|---|
volume24h |
We don't track trade events as volume yet. TODO. |
pnlPct (balance) |
Would require a cost-basis tracker per user. |
marginType |
ftDNMM is cross-margin only — hardcoded "cross" is correct. |
| TWAP endpoints | No on-chain TWAP infra. Empty array until off-chain keeper is built. |
POST /order / DELETE /order/:id |
501 — executor owns wallet-signed writes. Stays that way. |
Indexer reads SPOT_ORACLE_ROUTER_ADDR and SPOT_LENDING_LENSE_ADDR from ECS env. Both are set on dev. If either is missing, enrichment falls back to placeholders (graceful).