Trade History & Portfolio
Base URL: https://service.leverup.xyz
Trade history
Every protocol event affecting a trader, decoded and joined against the position it belongs to. This is the easiest way to build an activity feed without replaying onchain logs.
GET /v1/user/{user_address}/trade/history| Parameter | Default | Notes |
|---|---|---|
page | 0 | |
size | 10 | |
block_chain | MONAD |
Response
{
"content": [
{
"id": "…",
"transactionHash": "0x…",
"blockNumber": 1234567,
"transactionIndex": 3,
"logIndex": 7,
"hash": "0x…",
"positionHash": "0x…",
"pairBase": "0xcf5a…",
"qty": "10000000",
"entryPrice": "100123450000000000000000",
"closePrice": null,
"pnl": null,
"tradeType": "MARKET",
"operationType": "OPEN_POSITION",
"blockTime": "2026-07-27T10:00:00Z",
"isLong": true,
"tokenIn": "0x7547…",
"lvToken": "0xfd44…",
"amountIn": "10000000",
"margin": "9930000000000000000",
"tokenInPrice": "1000000000000000000",
"position": { },
"detail": { }
}
],
"pageNumber": 0,
"pageSize": 10,
"totalPages": 12,
"totalElements": 116
}position embeds the same object returned by Positions, so a single history page gives you both the event and the position state.
Units follow the onchain conventions: qty at 1e10, entryPrice / closePrice at 1e18, amountIn in tokenIn decimals, and margin / pnl in lvToken decimals. See Precision & Units.
operationType
Each value corresponds to the onchain event of the same name — see Events.
| Group | Values |
|---|---|
| Opening | MARKET_PENDING_TRADE, OPEN_POSITION, POSITION_INCREASED, PENDING_TRADE_REFUND |
| Closing | CLOSE_POSITION, POSITION_DECREASED, EXECUTE_CLOSE_SUCCESSFUL, EXECUTE_CLOSE_REJECTED |
| Limit orders | OPEN_LIMIT_ORDER, EXECUTE_LIMIT_ORDER_SUCCESSFUL, EXECUTE_LIMIT_ORDER_REJECTED, CANCEL_LIMIT_ORDER, LIMIT_ORDER_REFUND, UPDATE_ORDER_TP, UPDATE_ORDER_SL |
| TP/SL orders | DECREASE_ORDER_CREATED, DECREASE_ORDER_UPDATED, DECREASE_ORDER_CANCELLED, EXECUTE_DECREASE_ORDER_SUCCESSFUL |
| Position management | UPDATE_MARGIN, UPDATE_TRADE_TP, UPDATE_TRADE_SL |
Older records may carry OPEN_MARKET_TRADE and CLOSE_TRADE_SUCCESSFUL from the pre-merge event model. Treat them as equivalent to OPEN_POSITION and CLOSE_POSITION when rendering history.
async function activityFeed(address: string) {
const res = await fetch(`${API}/v1/user/${address}/trade/history?size=50`)
const { content } = await res.json()
return content.map((e: any) => ({
at: e.blockTime,
what: e.operationType,
pair: e.position?.pair ?? e.pairBase,
tx: e.transactionHash,
}))
}Current unrealized PnL
Returns the current USD valuation of all of a trader's POOL positions on Monad, including legacy and merged positions. This is an account total, with no pagination or time-range filter. Cross-Exchange positions are excluded.
GET /v1/portfolio/{user_address}/unrealized-pnl?block_chain=MONAD| Parameter | Location | Required | Notes |
|---|---|---|---|
user_address | Path | yes | A valid EVM wallet address; returned lowercase |
block_chain | Query | no | Defaults to MONAD; use this spelling, not blockchain |
No authentication or wallet signature is required. Mainnet example (replace the address with the wallet you want to query):
curl --fail-with-body \
'https://service.leverup.xyz/v1/portfolio/0x0000000000000000000000000000000000000001/unrealized-pnl?block_chain=MONAD'Response fields
All four monetary fields are decimal USD strings: "12.5" means $12.50, not a scaled integer. Do not divide them by 1e18. Keep strings or use decimal arithmetic when calculating with these values; see Precision & Units.
| Field | Type | Meaning |
|---|---|---|
userAddress | string | Lowercase wallet address |
blockChain | string | MONAD |
positionCount | integer | Number of onchain positions included |
unrealizedPnlUsd | string | Mark-to-market PnL, with each position's loss capped at its margin |
unrealizedPnlAfterAccruedFeesUsd | string | PnL including accrued funding and holding fees, capped independently per position |
fundingFeeUsd | string | Total funding: positive is income, negative is an expense |
holdingFeeUsd | string | Total accrued holding expense, reported as a nonnegative amount |
blockNumber | string | Block used to read positions and their accrued fees |
blockTimestamp | integer | Timestamp of that block, in Unix seconds |
priceAsOf | integer, null or absent | Earliest publication time among the quotes used, in Unix seconds; null or omitted for an empty account |
Illustrative response:
{
"userAddress": "0x0000000000000000000000000000000000000001",
"blockChain": "MONAD",
"positionCount": 1,
"unrealizedPnlUsd": "10",
"unrealizedPnlAfterAccruedFeesUsd": "12",
"fundingFeeUsd": "3",
"holdingFeeUsd": "1",
"blockNumber": "108450856",
"blockTimestamp": 1790507755,
"priceAsOf": 1790507755
}With no positions, positionCount is 0 and all monetary fields are "0". The response still includes the current position-snapshot block; priceAsOf is null or omitted. A failed query is not an empty account.
Valuation and fees
For each position, the service calculates signed PnL from its quantity, entry price and current mark price, then values the result using its settlement token (marginToken). The loss floor is that position's margin. In settlement-token units, before conversion to USD:
gross = max(rawPnl, -margin)
net = max(rawPnl + fundingFee - holdingFee, -margin)The two floors are applied independently before summing the USD totals. Consequently, unrealizedPnlAfterAccruedFeesUsd cannot always be reconstructed by adding the aggregate funding and holding amounts to the already-capped unrealizedPnlUsd. Integer rounding can also leave tiny differences.
Accrued fees are already included; do not add them again. Paid opening fees are not deducted again, and the net field excludes estimated closing fees, execution fees and slippage. It is a current valuation, not a guaranteed amount receivable on closing. Realized PnL from Portfolio data and Trading stats is separate.
Freshness, caching and errors
Positions and accrued fees are read from one onchain block. Quotes are fetched separately, so blockTimestamp and priceAsOf need not be equal. The endpoint does not wait for the historical statistics publication window.
Successful responses carry Cache-Control: no-store. The current freshness limit for both the position snapshot and quotes is 30 seconds, with up to 5 seconds of future clock skew; valuation has a 10-second processing timeout. Client/network latency is additional. A recently retrieved DEX quote can still have an old publication time and be rejected, including when a market has stopped publishing fresh prices.
| Status | Meaning | Client handling |
|---|---|---|
200 | Complete valuation, including an empty account | Display the returned decimal USD amounts |
400 | Invalid address or invalid chain parameter | Correct the request |
503 | Complete current valuation unavailable: RPC failure/timeout, missing token information, missing/nonpositive/stale quotes, or an explicitly unsafe quote | Show an unavailable state and retry with backoff; do not substitute zero or a partial total |
One unavailable position or required quote causes the entire valuation to fail. Optional price safety signals are honored when supplied; a successful response does not mean every provider supplied an independent safety verdict. Clients should branch on the HTTP status rather than matching error-message text.
Trade overview
Headline PnL numbers for a trader.
GET /v1/portfolio/{user_address}/trade_overview?timeframe=DAYS_30&is_after_fee=true| Parameter | Required | Notes |
|---|---|---|
timeframe | yes | ALL_TIME, DAYS_7, DAYS_30, DAYS_60 |
is_after_fee | yes | Whether PnL is net of fees |
blockchain | no | Defaults to MONAD |
{
"realizedPnlUsd": "1234.56",
"biggestWinUsd": "890.12",
"winRate": 0.62,
"updatedAt": "2026-07-27T10:00:00Z"
}winRate is a fraction, not a percentage. Cached for 30 seconds.
PnL chart
Time series for a PnL chart.
GET /v1/portfolio/{user_address}/pnl_chart_data?timeframe=DAYS_30&is_after_fee=true&timezone=UTC| Parameter | Required | Notes |
|---|---|---|
timeframe | yes | ALL_TIME, DAYS_7, DAYS_30, DAYS_60 |
is_after_fee | yes | |
timezone | yes | Determines day boundaries, e.g. UTC, Asia/Shanghai |
blockchain | no |
{
"data": [
{ "t": 1785225600, "pnl": "120.50" },
{ "t": 1785312000, "pnl": "-45.20" }
],
"updatedAt": "2026-07-27T10:00:00Z",
"resolution": "DAY_1"
}t is a Unix second bucket start. resolution is DAY_1, DAY_7 or DAY_30, chosen by the server based on the timeframe. Cached for 30 seconds.
Portfolio data
Aggregate breakdown across markets.
GET /v1/portfolio/{user_address}/portfolio_data{
"trader": "0x…",
"realizedPNL": "1500000000000000000000",
"realizedPNLAfterFees": "1234560000000000000000",
"tradingVolumeUSD": "250000000000000000000000",
"gain": 31,
"loss": 19,
"metrics": {
"positionLiveCount": 2,
"orderLiveCount": 1
},
"tradingPairs": [
{
"pairBase": "0xcf5a…",
"pairName": "BTC/USD",
"pairSymbol": "BTC",
"pairType": "CRYPTO",
"tradingVolumeQty": "125000000000",
"tradingVolumeUSD": "180000000000000000000000"
}
]
}realizedPNL, realizedPNLAfterFees and tradingVolumeUSD are USD scaled by 1e18; tradingVolumeQty is base-asset quantity scaled by 1e10. These units differ from the already decimal USD amounts in Current unrealized PnL. See Precision & Units.
gain and loss are counts of profitable and unprofitable closed positions.