Security & Trust
Levant is unaudited software running on a public testnet. This page states plainly what you are trusting today, which risks are not yet mitigated, and which on-chain guards bound your loss — so you can decide for yourself rather than take our word for it.
Security posture: honest and early
Levant is a set of EIP-2535 Diamond smart contracts plus an off-chain keeper. The contracts are built with Foundry on Solidity 0.8.28 and ship with 171 passing tests, but they are unaudited and run only on Robinhood Chain testnet (chainId 4663). Tests and careful design reduce risk; they do not eliminate it. Treat everything here as experimental. The sections below describe exactly who and what you are trusting, and where the sharp edges are.
What Levant is — and is not — today
- Testnet only. Deployed on Robinhood Chain mainnet, chainId 4663. Gas is Sepolia ETH. Nothing here has real-world value.
- Unaudited. No third-party audit has been performed. 171 unit/integration tests pass, but that is not an audit.
- Mock collateral. USDG is a
MockStabletest token (6 decimals) with an open mint — anyone can mint themselves test funds. There is no canonical USDG on testnet, and pretending otherwise would be dishonest. - $LVNT is separate from the exchange. Levant's token, $LVNT, launched on Virtuals and is not required to trade; the exchange runs on USDG.
lvUSDGis only the ERC-4626 share receipt for the liquidity vault, not the token. - Single operator. One keeper runs pricing and execution today. It is a single point of failure for liveness (see below).
- Non-custodial. Your collateral is escrowed by the Diamond contract, never by the keeper. No operator can move your funds at will.
The trust model right now
Every component sits somewhere on a spectrum from trustless to fully trusted. Here is where each one actually stands today, with no rounding up.
| Component | Status today | What you are trusting |
|---|---|---|
| Contracts | Unaudited, 171 tests | That the code is correct and the tests are representative |
| Price oracle | 1-of-1 signed (single keeper key) | That one signing key is not compromised or misused |
| Ownership | Deployer hot key | That one key controls all upgrades and every risk parameter |
| Liquidations | Permissionless | Anyone with a valid signed price can liquidate an underwater position |
| Custody of funds | Escrowed by the Diamond | The contract holds your collateral — the keeper never does |
| Collateral (USDG) | Mock token, open mint | Testnet only; no monetary value |
Custody: the contract holds your funds, not the keeper
Trading is a two-step flow, and the split is what keeps it non-custodial. Step 1: you escrow collateral on-chain — submitOpenIntent for a market order, submitLimitIntent for a limit or stop. Step 2: a keeper fills the intent with a signed price report via executeOpen. Closing is symmetric: closePosition or closePartial, then executeClose. At no point does the keeper hold your money — funds are locked in the Diamond and released only by the contract's own logic. The keeper is bounded by on-chain guards: it can only ever fill at a verified price that also passes the slippage and liquidatable-at-open checks. Its key is funded with gas only.
The oracle and why it must be authenticated
Prices come from SignedPriceOracleFacet, a self-hosted EIP-712 m-of-n signed-price oracle. This design exists for one reason: permissionless liquidation is only safe when a price cannot be fabricated. liquidatePosition accepts a price report from *any* caller, so if the oracle did not authenticate its input, anyone could liquidate anyone at an invented price and collect the 5% reward. The signed oracle closes that hole by verifying every price on-chain before the engine will act on it.
Each price is an EIP-712 PriceReport signed by whitelisted signers. The Diamond re-derives the digest and checks a chain of conditions before returning a price; if any fails, the whole action (open, close, or liquidation) reverts.
EIP712Domain { name: "Levant", version: "1", chainId: 4663, verifyingContract: <the Diamond> }
PriceReport { bytes32 marketId, uint256 price1e18, uint64 publishTime }
getVerifiedPrice rejects the report unless ALL hold:
reportMarketId == the market being priced // else MarketMismatch (no cross-market replay)
price1e18 != 0 // else BadPrice
publishTime <= block.timestamp + 5s // else FutureTimestamp (clock-skew guard)
block.timestamp <= publishTime + 60s // else StalePrice (maxPriceAge = 60s)
signatures.length >= threshold // else ThresholdNotMet
recovered signers strictly ascending + whitelisted// else SignersOutOfOrder / NotSigner
// chainId + verifyingContract in the domain prevent cross-chain / cross-contract replay.
// OpenZeppelin ECDSA rejects malleable (high-s) and invalid-v signatures.- Forged prices are blocked by ECDSA over the EIP-712 digest; only owner-whitelisted keys can sign.
- A single rogue key is contained by the m-of-n threshold — once more than one signer exists.
- Duplicate signatures cannot pad a quorum: recovered signers must strictly ascend, so one key cannot satisfy the threshold by signing m times.
- Stale prices are rejected 60 seconds after
publishTime; a price dated more than 5 seconds in the future is rejected too. - Cross-market and cross-chain replay are blocked by binding
marketIdinside the struct andchainId+ the Diamond address in the domain.
Under the hood, prices are sourced from a multi-provider feed — Gate.io first, then KuCoin, then CoinGecko as a fallback — and signed in chain time, so the keeper corrects for the chain clock running behind wall-clock rather than tripping the staleness check. removePriceSigner refuses to leave the threshold unsatisfiable, and setMaxPriceAge rejects a zero window, so an owner cannot accidentally freeze pricing (which would freeze every open, close, and liquidation).
On-chain guards that bound your risk
Independent of who the operator is, several guards are enforced by the contract itself. They cap the price you can be filled at, refuse trades that are dead on arrival, and bound your maximum loss to the margin you posted.
| Guard | Rule | Effect on a violation |
|---|---|---|
| Slippage tolerance | Every open/limit intent carries a mandatory maxSlippageBps in [1, 500] — no implicit default | executeOpen reverts SlippageExceeded; intent stays pending and escrow is reclaimable with cancelIntent |
| Liquidatable-at-open | Refuse a position already at/under 10% maintenance margin at the fill mark | Reverts LiquidatableAtOpen; no open fee is taken |
| Spread floor | A fill is never better than mid | Total spread floored at 0 bps |
| Spread cap | The spread can never exceed 5% | Total spread capped at 500 bps |
| Isolated margin | Each position's loss is bounded to its own margin | One position can never drain another |
| Max-profit reserve | Vault locks margin x 9 per position | Guarantees the capped 900%-of-margin payout is funded |
spreadBps = clamp( 2 + priceImpact + skew , 0 , 500 ) // >= 0 (never beats mid), <= 500 (5% cap)
execPrice = mid * (1 +/- spreadBps/1e4) // applied worse-for-trade
executeOpen reverts SlippageExceeded if the spread pushes entry past your maxSlippageBps (1..500)
executeOpen reverts LiquidatableAtOpen if the new position is already <= 10% maintenance at the fill
// on either revert: the intent is NOT consumed and no open fee is charged
isolated margin -> max loss = your margin
reserved = margin x 9 // locked in the vault to fund the 900% max-profit capThe slippage guard is not cosmetic. At 100x leverage a 1% entry spread is a 100% loss before the position even exists, so the liquidatable-at-open check refuses to mint a position that would be dead on arrival, and no fee is taken. In the UI you pick a Max slippage — 0.10%, 0.30%, 0.50%, or 1.00% — and the engine holds you to it.
Permissionless liquidation
Liquidation is open to anyone. A position becomes liquidatable when its equity falls to or below the 10% maintenance margin — roughly a 90% loss including accrued funding. Any caller can close it by submitting a signed oracle price report; the contract re-checks the condition against that verified price, so a liquidation only settles if the position is genuinely underwater. The liquidator is paid 5% of collateral for keeping the book solvent, and no adverse spread is applied on the liquidation mark. Because margin is isolated, only the liquidated position is affected — never your other positions or unrelated traders.
Governance and upgrades
Levant is one EIP-2535 Diamond address with many facets over append-only Diamond-Storage. Upgrades are performed with diamondCut, which preserves the vault, LP shares, and every open position — storage fields are only ever appended, never reordered. The owner is the most powerful account in the system: it can diamondCut any facet, add or remove price signers, set the signer threshold, and rewrite every market risk parameter. That authority is real, so treat it as part of your risk assessment.
Ownership uses the two-step ERC-173 pattern (OwnershipFacet). transferOwnership only nominates a new owner; nothing changes until that nominee calls acceptOwnership, which proves the address is controllable and cannot be a typo, an exchange address, or a contract that can never call back. cancelOwnershipTransfer withdraws a pending nomination. There is deliberately no `renounceOwnership` — an ownerless diamond could never be cut again, so a future bug would be permanent.
The keeper is a single point of failure for liveness
The keeper is off-chain and non-custodial, but it is currently the only one. While it is down, nothing fills, nothing closes, and nothing liquidates. Open positions are frozen, not safe — the mark can keep moving against you while no keeper is available to execute your stop-loss or close. This is a liveness risk you carry on top of market risk, and it is one of the reasons this is a testnet document.
Verify it yourself — don't trust us
Every claim on this page is checkable on-chain. Read the live state of the Diamond directly rather than trusting this doc or any UI. Everything below is public and read-only.
| What | Address |
|---|---|
| Diamond (all user actions) | 0xE07098bc29Ebe50E6203606bc2eA0e1E189cb7e2 |
| Owner (deployer key, today) | 0x520F136b72Cc7A24FDB1C073189704442e9945F6 |
| Price signer / keeper | 0x5040b3387F450Af658f8E7C93ca3F0D44b07E7e4 |
| Vault (lvUSDG, ERC-4626) | 0xC90D98cFeE95F481aDE30d95AD88f01B6C65ad5C |
| USDG (mock collateral, open mint) | 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168 |
# Robinhood Chain mainnet, chainId 4663
# explorer: https://robinhoodchain.blockscout.com
DIAMOND=0xE07098bc29Ebe50E6203606bc2eA0e1E189cb7e2
cast call $DIAMOND "owner()(address)" # -> 0x520F...945F6 (deployer key, today)
cast call $DIAMOND "priceSignerCount()(uint8)" # -> 1 (1-of-1 oracle today)
cast call $DIAMOND "signerThreshold()(uint8)" # -> 1
cast call $DIAMOND "maxPriceAge()(uint64)" # -> 60 (seconds)
cast call $DIAMOND "isPriceSigner(address)(bool)" \
0x5040b3387F450Af658f8E7C93ca3F0D44b07E7e4 # -> trueReporting a vulnerability
If you find a security issue, please disclose it responsibly and privately. Do not open a public issue, post it on-chain, or exploit it against live positions. Report it privately to the maintainers — for example via a private security advisory on the project's source repository — and give them time to ship a fix. A diamondCut can patch a single facet without disturbing the vault, LP shares, or open positions, so a well-scoped report can usually be remediated quickly.
- Include the chain (chainId 4663), the affected contract address, and any transaction hashes.
- Describe the impact and give a concrete reproduction — a failing test or a script is ideal.
- This is unaudited testnet software with no bug-bounty program and no real funds at stake, so there is no reward on offer and no need to demonstrate an exploit against real value. A clear write-up is enough.
- Please do not publicly disclose until a fix has been deployed and verified on-chain.
Roadmap to a harder posture
- m-of-n oracle — run multiple signers on independent machines and raise the threshold, so no single key can price the exchange.
- Move ownership off the hot key — complete the two-step transfer to a hardware wallet or multisig.
- Chainlink Data Streams — the engine already exposes the same
getVerifiedPriceselector, so the signed oracle can be swapped for a DON-backed feed viadiamondCutwherever credentials are available. - Independent audit — before any deployment that holds real value.
