Core Concepts
The Diamond
Every contract interaction goes through a single address — the LeverUp Diamond (0xea1b8E4aB7f14F7dCA68c5B214303B13078FC5ec). Opening trades, limit orders, TP/SL orders, one-click trading, pair configuration and all read functions are facets behind that one proxy.
Practically, this means: point every readContract / writeContract at the Diamond address, and supply only the ABI fragment for the function you are calling.
Pairs
A market is identified by its pairBase — an address that acts as the pair's identifier. It is not a token you can hold or transfer; it is the key you pass to every trading and read function.
Pair addresses change as markets are listed and delisted. Fetch them from GET /v1/pairs rather than hardcoding.
Pairs carry a status:
| Status | Meaning |
|---|---|
AVAILABLE | Open and close both allowed |
REDUCE_ONLY | Only closes and reductions allowed |
CLOSE | Market disabled |
Non-crypto markets (equities, indices, forex, commodities) also have trading hours. GET /v1/pairs returns nextOpen / nextClose; attempting to trade a closed market reverts with MarketClosed.
Collateral and lvToken
Every position references two tokens:
tokenIn— what you deposit.lvToken— the protocol's settlement token for that position. Margin, fees and PnL are denominated in it.
The pairing is fixed:
tokenIn | Required lvToken |
|---|---|
| USDC | LVUSD |
| LVUSD | LVUSD |
Native MON (0x0 address) | LVMON |
| WMON | LVMON |
| LVMON | LVMON |
In short: USD-denominated collateral settles in LVUSD, MON-denominated collateral settles in LVMON. Passing a mismatched pair reverts.
You do not need to hold LV tokens to trade. Depositing USDC mints the LVUSD side internally. If you already hold LVUSD or LVMON you can pass it directly as tokenIn, which skips a conversion step.
WARNING
One-click trading only accepts ERC-20 collateral. To trade MON via 1CT, wrap it to WMON first.
Positions are merged slots
A position is keyed by (user, pairBase, isLong, lvToken), not by trade:
positionHash = keccak256(abi.encode(user, pairBase, isLong, lvToken, "position.v1"))Opening the same market and direction twice increases the existing position and blends the entry price — you never get two BTC-long positions with the same collateral. Consequences:
- To reduce exposure without exiting, use partial close.
- A position can carry multiple TP legs and one SL.
positionHashis stable and predictable, so you can compute it before the position exists.
Legacy positions
Positions created before the merged-position upgrade use a random hash instead of the deterministic key. They are full-close only and do not support TP/SL orders — they keep using the embedded updateTradeTp / updateTradeSl fields. Read functions return both kinds; a position whose hash does not match getPositionHash(...) for its own parameters is a legacy position.
Oracle updates
LeverUp settles against Pyth. Price-sensitive contract calls take an OracleUpdateData struct and a native-token fee alongside it:
struct OracleUpdateData {
bytes[] pythPriceUpdateData;
bytes[] pythProPriceUpdateData;
}You do not fetch this from Pyth directly. Request it from the LeverUp backend, which selects the right feeds for the pair and collateral and returns the fee to attach:
- Endpoint:
POST /v1/oracle/price/updates/by-position - Attach
updateFee + verifition_feeas the transactionvalue.
Payloads are short-lived. Fetch immediately before sending the transaction, not at the start of a long-running flow.
Calls that need oracle data: opening market and limit orders, removing margin, and closes. Calls that only change stored values — updating a take-profit, cancelling an order — do not.
1CT skips this entirely
When trading through 1CT, the relayer fetches oracle data for you. Intents never carry oracle payloads.
Fees
| Fee | When | Paid in |
|---|---|---|
| Open fee | On open | lvToken |
| Close fee | On close | lvToken |
| Holding fee | Accrues while open | lvToken |
| Funding fee | Accrues while open, signed | lvToken |
| Oracle update fee | Per price-sensitive transaction | Native MON |
| Execution fee (1CT only) | Per intent | Configurable ERC-20, see Execution Fee |
extraFee (optional) | Per open, if you set it | tokenIn — see Brokers & Referrals |
Fee rates are per pair and readable onchain via getPairFeeConfig. Some markets have a minimum holding period (minHoldingSeconds in GET /v1/pairs) before a position can be closed; a position also exposes earliestCloseTime.
Part of the open and close fee can be credited to a referral channel via the broker field that appears on every fee-charging call. This does not change what the trader pays — see Brokers & Referrals.
Slippage
Fills are not at the raw oracle price. Each pair has a slippage model that adjusts the fill based on open interest and trade size. This is why every open takes a price field — it is the worst acceptable price, not a limit price:
- Long: the maximum price you will accept.
- Short: the minimum price you will accept.
If the adjusted fill falls outside it, the trade is refunded rather than filled. See Reading Data for reading the model's parameters.
For limit orders, price is the actual limit price.
Keepers
Two things happen asynchronously, executed by protocol keepers:
- Fills. A pending open or close is settled once the keeper delivers the oracle price.
- Triggers. Limit orders, TP/SL orders and liquidations fire when the price condition is met.
Your transaction succeeding means the request was accepted, not that the position exists. Confirm via events or by polling getPositionsV4.
Next: Precision & Units, then pick an integration path — Onchain or Gasless.